am 5a193f9d: Merge "fix typo for AndroidManifest.xml"

* commit '5a193f9da6e87c7b15e627aedfb1e8f5d1df66e3':
  fix typo for AndroidManifest.xml
diff --git a/Android.mk b/Android.mk
index a5efe55..c17ee04 100644
--- a/Android.mk
+++ b/Android.mk
@@ -8,16 +8,24 @@
 LOCAL_STATIC_JAVA_LIBRARIES += com.android.gallery3d.common2
 LOCAL_STATIC_JAVA_LIBRARIES += xmp_toolkit
 LOCAL_STATIC_JAVA_LIBRARIES += mp4parser
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v8-renderscript
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
+LOCAL_RENDERSCRIPT_TARGET_API := 18
+LOCAL_RENDERSCRIPT_COMPATIBILITY := 18
+LOCAL_RENDERSCRIPT_FLAGS := -rs-package-name=android.support.v8.renderscript
+
+# Keep track of previously compiled RS files too (from bundled GalleryGoogle).
+prev_compiled_rs_files := $(call all-renderscript-files-under, src)
+
+# We already have these files from GalleryGoogle, so don't install them.
+LOCAL_RENDERSCRIPT_SKIP_INSTALL := $(prev_compiled_rs_files)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(prev_compiled_rs_files)
 LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
-LOCAL_SRC_FILES += $(call all-java-files-under, ../Camera/src)
 
-LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res 
-LOCAL_RESOURCE_DIR += packages/apps/Camera/res
+LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
 
-LOCAL_AAPT_FLAGS := --auto-add-overlay \
-    --extra-packages com.android.camera
+LOCAL_AAPT_FLAGS := --auto-add-overlay
 
 LOCAL_PACKAGE_NAME := Gallery2
 
@@ -29,7 +37,7 @@
 # the libraries in the APK, otherwise just put them in /system/lib and
 # leave them out of the APK
 ifneq (,$(TARGET_BUILD_APPS))
-  LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence libjni_filtershow_filters
+  LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_eglfence libjni_filtershow_filters librsjni
 else
   LOCAL_REQUIRED_MODULES := libjni_mosaic libjni_eglfence libjni_filtershow_filters
 endif
@@ -41,10 +49,8 @@
 include $(call all-makefiles-under, jni)
 
 ifeq ($(strip $(LOCAL_PACKAGE_OVERRIDES)),)
-# Use the following include to make gallery test apk.
-include $(call all-makefiles-under, $(LOCAL_PATH))
 
-# Use the following include to make camera test apk.
-include $(call all-makefiles-under, ../Camera)
+# Use the following include to make gallery test apk and the mosaic library
+include $(call all-makefiles-under, $(LOCAL_PATH))
 
 endif
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 21239e8..62acc00 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,13 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<manifest android:versionCode="40001"
-        android:versionName="1.1.40001"
+<manifest android:versionCode="40012"
+        android:versionName="1.1.40012"
         xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.gallery3d">
 
     <original-package android:name="com.android.gallery3d" />
 
-    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="16" />
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" />
 
     <permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER"
             android:protectionLevel="signatureOrSystem" />
@@ -20,7 +20,6 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
     <uses-permission android:name="android.permission.NFC" />
-    <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -31,6 +30,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+    <uses-permission android:name="com.android.gallery3d.permission.GALLERY_PROVIDER" />
 
     <supports-screens android:smallScreens="false"
             android:normalScreens="true" android:largeScreens="true"
@@ -41,8 +41,12 @@
             android:theme="@style/Theme.Gallery"
             android:logo="@mipmap/ic_launcher_gallery"
             android:hardwareAccelerated="true"
-            android:largeHeap="true">
+            android:largeHeap="true"
+            android:backupAgent="com.android.camera.CameraBackupAgent"
+            android:restoreAnyVersion="true">
         <uses-library android:name="com.google.android.media.effects" android:required="false" />
+        <meta-data android:name="com.google.android.backup.api_key"
+                android:value="AEdPqrEAAAAIRIXquXawbz6duuuCIUAZ_YJv1zbFMMcjZ0NoVw" />
         <activity android:name="com.android.gallery3d.app.MovieActivity"
                 android:label="@string/movie_view_label"
                 android:configChanges="orientation|keyboardHidden|screenSize">
@@ -103,6 +107,20 @@
                 <data android:mimeType="image/*" />
                 <data android:mimeType="video/*" />
             </intent-filter>
+            <!-- We do NOT support the PICK intent, we add these intent-filter for
+                 backward compatibility. Handle it as GET_CONTENT. -->
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="vnd.android.cursor.dir/image" />
+                <data android:mimeType="vnd.android.cursor.dir/video" />
+            </intent-filter>
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
@@ -123,6 +141,7 @@
                 <data android:mimeType="image/jpeg" />
                 <data android:mimeType="image/gif" />
                 <data android:mimeType="image/png" />
+                <data android:mimeType="image/webp" />
                 <data android:mimeType="image/x-ms-bmp" />
                 <data android:mimeType="image/vnd.wap.wbmp" />
                 <data android:mimeType="application/vnd.google.panorama360+jpg" />
@@ -142,20 +161,6 @@
                 <data android:mimeType="video/3gpp2" />
                 <data android:mimeType="application/sdp" />
             </intent-filter>
-            <!-- We do NOT support the PICK intent, we add these intent-filter for
-                 backward compatibility. Handle it as GET_CONTENT. -->
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="image/*" />
-                <data android:mimeType="video/*" />
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.intent.action.PICK" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:mimeType="vnd.android.cursor.dir/image" />
-                <data android:mimeType="vnd.android.cursor.dir/video" />
-            </intent-filter>
         </activity>
 
         <!-- we add this activity-alias for shortcut backward compatibility -->
@@ -169,16 +174,19 @@
             </intent-filter>
         </activity-alias>
 
-        <!-- This activity receives USB_DEVICE_ATTACHED Intents and springboards to main Gallery activity. -->
-        <activity android:name="com.android.gallery3d.app.UsbDeviceActivity" android:label="@string/app_name"
-                android:taskAffinity=""
-                android:launchMode="singleInstance">
+         <!-- This activity receives USB_DEVICE_ATTACHED intents and allows importing
+         media from attached MTP devices, like cameras and camera phones -->
+        <activity android:launchMode="singleInstance"
+            android:taskAffinity="" android:name="com.android.gallery3d.ingest.IngestActivity"
+            android:configChanges="orientation|screenSize"
+            android:label="@string/app_name">
             <intent-filter>
                 <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
             </intent-filter>
             <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                 android:resource="@xml/device_filter" />
         </activity>
+        <service android:name="com.android.gallery3d.ingest.IngestService" />
 
         <activity android:name="com.android.gallery3d.app.Wallpaper"
                 android:configChanges="keyboardHidden|orientation|screenSize"
@@ -195,23 +203,6 @@
             <meta-data android:name="android.wallpaper.preview"
                     android:resource="@xml/wallpaper_picker_preview" />
         </activity>
-        <activity android:name="com.android.gallery3d.app.CropImage"
-                android:configChanges="keyboardHidden|orientation|screenSize"
-                android:label="@string/crop_label"
-                android:process=":crop">
-            <intent-filter android:label="@string/crop_label">
-                <action android:name="com.android.camera.action.CROP" />
-                <data android:scheme="http" />
-                <data android:scheme="https" />
-                <data android:scheme="content" />
-                <data android:scheme="file" />
-                <data android:scheme="" />
-                <data android:mimeType="image/*" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.ALTERNATIVE" />
-                <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
-            </intent-filter>
-        </activity>
         <activity android:name="com.android.gallery3d.app.TrimVideo"
                 android:label="@string/trim_label">
         </activity>
@@ -243,8 +234,15 @@
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="image/*" />
             </intent-filter>
-            <intent-filter android:label="@string/crop_label">
-                <action android:name="com.android.camera.action.EDITOR_CROP" />
+        </activity>
+
+        <activity
+            android:name="com.android.gallery3d.filtershow.crop.CropActivity"
+            android:label="@string/crop"
+            android:theme="@style/Theme.FilterShow"
+            android:configChanges="keyboardHidden|orientation|screenSize">
+           <intent-filter android:label="@string/crop_label">
+                <action android:name="com.android.camera.action.CROP" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
                 <data android:scheme="content" />
@@ -270,6 +268,11 @@
                 android:exported="true"
                 android:permission="com.android.gallery3d.permission.GALLERY_PROVIDER"
                 android:authorities="com.android.gallery3d.provider" />
+        <provider
+                android:name="com.android.photos.data.PhotoProvider"
+                android:authorities="com.android.gallery3d.photoprovider"
+                android:syncable="false"
+                android:exported="false"/>
         <activity android:name="com.android.gallery3d.gadget.WidgetClickHandler" />
         <activity android:name="com.android.gallery3d.app.DialogPicker"
                 android:configChanges="keyboardHidden|orientation|screenSize"
@@ -305,7 +308,7 @@
                 android:taskAffinity="com.android.camera.SecureCameraActivity"
                 android:excludeFromRecents="true"
                 android:label="@string/camera_label"
-                android:theme="@style/Theme.CameraSecure"
+                android:theme="@style/Theme.Camera"
                 android:icon="@mipmap/ic_launcher_camera"
                 android:configChanges="orientation|screenSize|keyboardHidden"
                 android:clearTaskOnLaunch="true"
@@ -396,5 +399,7 @@
         <activity android:name="com.android.camera.ProxyLauncher"
                 android:theme="@style/Theme.ProxyLauncher">
         </activity>
+        <service android:name="com.android.gallery3d.app.BatchService" />
+        <service android:name="com.android.camera.MediaSaveService" />
     </application>
 </manifest>
diff --git a/CleanSpec.mk b/CleanSpec.mk
index cc930a1..20db309 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -47,6 +47,9 @@
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Camera*)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/Gallery*)
 $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Gallery*)
 
 # ************************************************
 # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 837777e..dde4c56 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -22,6 +22,7 @@
 import android.os.Build;
 import android.provider.MediaStore.MediaColumns;
 import android.view.View;
+import android.view.WindowManager;
 
 import java.lang.reflect.Field;
 
@@ -36,8 +37,11 @@
         public static final int ICE_CREAM_SANDWICH_MR1 = 15;
         public static final int JELLY_BEAN = 16;
         public static final int JELLY_BEAN_MR1 = 17;
+        public static final int JELLY_BEAN_MR2 = 18;
     }
 
+    public static final boolean AT_LEAST_16 = Build.VERSION.SDK_INT >= 16;
+
     public static final boolean USE_888_PIXEL_FORMAT =
             Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
 
@@ -170,6 +174,30 @@
     public static final boolean HAS_POST_ON_ANIMATION =
             Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
 
+    public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+    public static final boolean HAS_OBJECT_ANIMATION =
+            Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+    public static final boolean HAS_GLES20_REQUIRED =
+            Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+    public static final boolean HAS_ROTATION_ANIMATION =
+            hasField(WindowManager.LayoutParams.class, "rotationAnimation");
+
+    public static final boolean HAS_ORIENTATION_LOCK =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
+
+    public static final boolean HAS_CANCELLATION_SIGNAL =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+    public static final boolean HAS_MEDIA_MUXER =
+                    Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2;
+
+    public static final boolean HAS_DISPLAY_LISTENER =
+            Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1;
+
     public static int getIntFieldIfExists(Class<?> klass, String fieldName,
             Class<?> obj, int defaultVal) {
         try {
diff --git a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
deleted file mode 100644
index 9b11fe4..0000000
--- a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.common;
-
-/**
- * The class holds the EXIF tag names that are not available in
- * {@link android.media.ExifInterface} prior to API level 11.
- */
-public interface ExifTags {
-    static final String TAG_ISO = "ISOSpeedRatings";
-    static final String TAG_EXPOSURE_TIME = "ExposureTime";
-    static final String TAG_APERTURE = "FNumber";
-}
diff --git a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
deleted file mode 100644
index b3298e6..0000000
--- a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java
+++ /dev/null
@@ -1,368 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.common;
-
-
-// Copied from android.util.LongSparseArray for unbundling
-
-/**
- * SparseArray mapping longs to Objects.  Unlike a normal array of Objects,
- * there can be gaps in the indices.  It is intended to be more efficient
- * than using a HashMap to map Longs to Objects.
- */
-public class LongSparseArray<E> implements Cloneable {
-    private static final Object DELETED = new Object();
-    private boolean mGarbage = false;
-
-    private long[] mKeys;
-    private Object[] mValues;
-    private int mSize;
-
-    /**
-     * Creates a new LongSparseArray containing no mappings.
-     */
-    public LongSparseArray() {
-        this(10);
-    }
-
-    /**
-     * Creates a new LongSparseArray containing no mappings that will not
-     * require any additional memory allocation to store the specified
-     * number of mappings.
-     */
-    public LongSparseArray(int initialCapacity) {
-        initialCapacity = idealLongArraySize(initialCapacity);
-
-        mKeys = new long[initialCapacity];
-        mValues = new Object[initialCapacity];
-        mSize = 0;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public LongSparseArray<E> clone() {
-        LongSparseArray<E> clone = null;
-        try {
-            clone = (LongSparseArray<E>) super.clone();
-            clone.mKeys = mKeys.clone();
-            clone.mValues = mValues.clone();
-        } catch (CloneNotSupportedException cnse) {
-            /* ignore */
-        }
-        return clone;
-    }
-
-    /**
-     * Gets the Object mapped from the specified key, or <code>null</code>
-     * if no such mapping has been made.
-     */
-    public E get(long key) {
-        return get(key, null);
-    }
-
-    /**
-     * Gets the Object mapped from the specified key, or the specified Object
-     * if no such mapping has been made.
-     */
-    @SuppressWarnings("unchecked")
-    public E get(long key, E valueIfKeyNotFound) {
-        int i = binarySearch(mKeys, 0, mSize, key);
-
-        if (i < 0 || mValues[i] == DELETED) {
-            return valueIfKeyNotFound;
-        } else {
-            return (E) mValues[i];
-        }
-    }
-
-    /**
-     * Removes the mapping from the specified key, if there was any.
-     */
-    public void delete(long key) {
-        int i = binarySearch(mKeys, 0, mSize, key);
-
-        if (i >= 0) {
-            if (mValues[i] != DELETED) {
-                mValues[i] = DELETED;
-                mGarbage = true;
-            }
-        }
-    }
-
-    /**
-     * Alias for {@link #delete(long)}.
-     */
-    public void remove(long key) {
-        delete(key);
-    }
-
-    /**
-     * Removes the mapping at the specified index.
-     */
-    public void removeAt(int index) {
-        if (mValues[index] != DELETED) {
-            mValues[index] = DELETED;
-            mGarbage = true;
-        }
-    }
-
-    private void gc() {
-        // Log.e("SparseArray", "gc start with " + mSize);
-
-        int n = mSize;
-        int o = 0;
-        long[] keys = mKeys;
-        Object[] values = mValues;
-
-        for (int i = 0; i < n; i++) {
-            Object val = values[i];
-
-            if (val != DELETED) {
-                if (i != o) {
-                    keys[o] = keys[i];
-                    values[o] = val;
-                    values[i] = null;
-                }
-
-                o++;
-            }
-        }
-
-        mGarbage = false;
-        mSize = o;
-
-        // Log.e("SparseArray", "gc end with " + mSize);
-    }
-
-    /**
-     * Adds a mapping from the specified key to the specified value,
-     * replacing the previous mapping from the specified key if there
-     * was one.
-     */
-    public void put(long key, E value) {
-        int i = binarySearch(mKeys, 0, mSize, key);
-
-        if (i >= 0) {
-            mValues[i] = value;
-        } else {
-            i = ~i;
-
-            if (i < mSize && mValues[i] == DELETED) {
-                mKeys[i] = key;
-                mValues[i] = value;
-                return;
-            }
-
-            if (mGarbage && mSize >= mKeys.length) {
-                gc();
-
-                // Search again because indices may have changed.
-                i = ~binarySearch(mKeys, 0, mSize, key);
-            }
-
-            if (mSize >= mKeys.length) {
-                int n = idealLongArraySize(mSize + 1);
-
-                long[] nkeys = new long[n];
-                Object[] nvalues = new Object[n];
-
-                // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
-                System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
-                System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
-                mKeys = nkeys;
-                mValues = nvalues;
-            }
-
-            if (mSize - i != 0) {
-                // Log.e("SparseArray", "move " + (mSize - i));
-                System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i);
-                System.arraycopy(mValues, i, mValues, i + 1, mSize - i);
-            }
-
-            mKeys[i] = key;
-            mValues[i] = value;
-            mSize++;
-        }
-    }
-
-    /**
-     * Returns the number of key-value mappings that this LongSparseArray
-     * currently stores.
-     */
-    public int size() {
-        if (mGarbage) {
-            gc();
-        }
-
-        return mSize;
-    }
-
-    /**
-     * Given an index in the range <code>0...size()-1</code>, returns
-     * the key from the <code>index</code>th key-value mapping that this
-     * LongSparseArray stores.
-     */
-    public long keyAt(int index) {
-        if (mGarbage) {
-            gc();
-        }
-
-        return mKeys[index];
-    }
-
-    /**
-     * Given an index in the range <code>0...size()-1</code>, returns
-     * the value from the <code>index</code>th key-value mapping that this
-     * LongSparseArray stores.
-     */
-    @SuppressWarnings("unchecked")
-    public E valueAt(int index) {
-        if (mGarbage) {
-            gc();
-        }
-
-        return (E) mValues[index];
-    }
-
-    /**
-     * Given an index in the range <code>0...size()-1</code>, sets a new
-     * value for the <code>index</code>th key-value mapping that this
-     * LongSparseArray stores.
-     */
-    public void setValueAt(int index, E value) {
-        if (mGarbage) {
-            gc();
-        }
-
-        mValues[index] = value;
-    }
-
-    /**
-     * Returns the index for which {@link #keyAt} would return the
-     * specified key, or a negative number if the specified
-     * key is not mapped.
-     */
-    public int indexOfKey(long key) {
-        if (mGarbage) {
-            gc();
-        }
-
-        return binarySearch(mKeys, 0, mSize, key);
-    }
-
-    /**
-     * Returns an index for which {@link #valueAt} would return the
-     * specified key, or a negative number if no keys map to the
-     * specified value.
-     * Beware that this is a linear search, unlike lookups by key,
-     * and that multiple keys can map to the same value and this will
-     * find only one of them.
-     */
-    public int indexOfValue(E value) {
-        if (mGarbage) {
-            gc();
-        }
-
-        for (int i = 0; i < mSize; i++)
-            if (mValues[i] == value)
-                return i;
-
-        return -1;
-    }
-
-    /**
-     * Removes all key-value mappings from this LongSparseArray.
-     */
-    public void clear() {
-        int n = mSize;
-        Object[] values = mValues;
-
-        for (int i = 0; i < n; i++) {
-            values[i] = null;
-        }
-
-        mSize = 0;
-        mGarbage = false;
-    }
-
-    /**
-     * Puts a key/value pair into the array, optimizing for the case where
-     * the key is greater than all existing keys in the array.
-     */
-    public void append(long key, E value) {
-        if (mSize != 0 && key <= mKeys[mSize - 1]) {
-            put(key, value);
-            return;
-        }
-
-        if (mGarbage && mSize >= mKeys.length) {
-            gc();
-        }
-
-        int pos = mSize;
-        if (pos >= mKeys.length) {
-            int n = idealLongArraySize(pos + 1);
-
-            long[] nkeys = new long[n];
-            Object[] nvalues = new Object[n];
-
-            // Log.e("SparseArray", "grow " + mKeys.length + " to " + n);
-            System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length);
-            System.arraycopy(mValues, 0, nvalues, 0, mValues.length);
-
-            mKeys = nkeys;
-            mValues = nvalues;
-        }
-
-        mKeys[pos] = key;
-        mValues[pos] = value;
-        mSize = pos + 1;
-    }
-
-    private static int binarySearch(long[] a, int start, int len, long key) {
-        int high = start + len, low = start - 1, guess;
-
-        while (high - low > 1) {
-            guess = (high + low) / 2;
-
-            if (a[guess] < key)
-                low = guess;
-            else
-                high = guess;
-        }
-
-        if (high == start + len)
-            return ~(start + len);
-        else if (a[high] == key)
-            return high;
-        else
-            return ~high;
-    }
-
-    private static int idealByteArraySize(int need) {
-        for (int i = 4; i < 32; i++)
-            if (need <= (1 << i) - 12)
-                return (1 << i) - 12;
-
-        return need;
-    }
-
-    public static int idealLongArraySize(int need) {
-        return idealByteArraySize(need * 8) / 8;
-    }
-}
diff --git a/gallerycommon/src/com/android/gallery3d/common/Utils.java b/gallerycommon/src/com/android/gallery3d/common/Utils.java
index 3a68745..614a081 100644
--- a/gallerycommon/src/com/android/gallery3d/common/Utils.java
+++ b/gallerycommon/src/com/android/gallery3d/common/Utils.java
@@ -26,6 +26,7 @@
 import android.util.Log;
 
 import java.io.Closeable;
+import java.io.IOException;
 import java.io.InterruptedIOException;
 
 public class Utils {
@@ -173,8 +174,8 @@
         if (c == null) return;
         try {
             c.close();
-        } catch (Throwable t) {
-            Log.w(TAG, "close fail", t);
+        } catch (IOException t) {
+            Log.w(TAG, "close fail ", t);
         }
     }
 
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java
new file mode 100644
index 0000000..7fb9f22
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+class ByteBufferInputStream extends InputStream {
+
+    private ByteBuffer mBuf;
+
+    public ByteBufferInputStream(ByteBuffer buf) {
+        mBuf = buf;
+    }
+
+    @Override
+    public int read() {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+        return mBuf.get() & 0xFF;
+    }
+
+    @Override
+    public int read(byte[] bytes, int off, int len) {
+        if (!mBuf.hasRemaining()) {
+            return -1;
+        }
+
+        len = Math.min(len, mBuf.remaining());
+        mBuf.get(bytes, off, len);
+        return len;
+    }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
index 39eb574..8422382 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
@@ -16,65 +16,70 @@
 
 package com.android.gallery3d.exif;
 
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
- *  This class stores the EXIF header in IFDs according to the JPEG specification.
- *  It is the result produced by {@link ExifReader}.
- *  @see ExifReader
- *  @see IfdData
+ * This class stores the EXIF header in IFDs according to the JPEG
+ * specification. It is the result produced by {@link ExifReader}.
+ *
+ * @see ExifReader
+ * @see IfdData
  */
-public class ExifData {
+class ExifData {
+    private static final String TAG = "ExifData";
+    private static final byte[] USER_COMMENT_ASCII = {
+            0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_JIS = {
+            0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+    };
+    private static final byte[] USER_COMMENT_UNICODE = {
+            0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+    };
+
     private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
     private byte[] mThumbnail;
     private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
     private final ByteOrder mByteOrder;
 
-    public ExifData(ByteOrder order) {
+    ExifData(ByteOrder order) {
         mByteOrder = order;
     }
 
-    IfdData getIfdData(int ifdId) {
-        return mIfdDatas[ifdId];
-    }
-
     /**
-     * Adds IFD data. If IFD data of the same type already exists,
-     * it will be replaced by the new data.
-     */
-    void addIfdData(IfdData data) {
-        mIfdDatas[data.getId()] = data;
-    }
-
-    /**
-     * Gets the compressed thumbnail. Returns null if there is no compressed thumbnail.
+     * Gets the compressed thumbnail. Returns null if there is no compressed
+     * thumbnail.
      *
      * @see #hasCompressedThumbnail()
      */
-    public byte[] getCompressedThumbnail() {
+    protected byte[] getCompressedThumbnail() {
         return mThumbnail;
     }
 
     /**
      * Sets the compressed thumbnail.
      */
-    public void setCompressedThumbnail(byte[] thumbnail) {
+    protected void setCompressedThumbnail(byte[] thumbnail) {
         mThumbnail = thumbnail;
     }
 
     /**
      * Returns true it this header contains a compressed thumbnail.
      */
-    public boolean hasCompressedThumbnail() {
+    protected boolean hasCompressedThumbnail() {
         return mThumbnail != null;
     }
 
     /**
      * Adds an uncompressed strip.
      */
-    public void setStripBytes(int index, byte[] strip) {
+    protected void setStripBytes(int index, byte[] strip) {
         if (index < mStripBytes.size()) {
             mStripBytes.set(index, strip);
         } else {
@@ -88,98 +93,57 @@
     /**
      * Gets the strip count.
      */
-    public int getStripCount() {
+    protected int getStripCount() {
         return mStripBytes.size();
     }
 
     /**
      * Gets the strip at the specified index.
+     *
      * @exceptions #IndexOutOfBoundException
      */
-    public byte[] getStrip(int index) {
+    protected byte[] getStrip(int index) {
         return mStripBytes.get(index);
     }
 
     /**
+     * Returns true if this header contains uncompressed strip.
+     */
+    protected boolean hasUncompressedStrip() {
+        return mStripBytes.size() != 0;
+    }
+
+    /**
      * Gets the byte order.
      */
-    public ByteOrder getByteOrder() {
+    protected ByteOrder getByteOrder() {
         return mByteOrder;
     }
 
     /**
-     * Returns true if this header contains uncompressed strip of thumbnail.
+     * Returns the {@link IfdData} object corresponding to a given IFD if it
+     * exists or null.
      */
-    public boolean hasUncompressedStrip() {
-        return mStripBytes.size() != 0;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj instanceof ExifData) {
-            ExifData data = (ExifData) obj;
-            if (data.mByteOrder != mByteOrder
-                    || !Arrays.equals(data.mThumbnail, mThumbnail)
-                    || data.mStripBytes.size() != mStripBytes.size()) return false;
-
-            for (int i = 0; i < mStripBytes.size(); i++) {
-                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false;
-            }
-
-            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
-                if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false;
-            }
-            return true;
+    protected IfdData getIfdData(int ifdId) {
+        if (ExifTag.isValidIfd(ifdId)) {
+            return mIfdDatas[ifdId];
         }
-        return false;
+        return null;
     }
 
     /**
-     * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE},
-     * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the
-     * given latitude and longitude.
+     * Adds IFD data. If IFD data of the same type already exists, it will be
+     * replaced by the new data.
      */
-    public void addGpsTags(double latitude, double longitude) {
-        IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS);
-        if (gpsIfd == null) {
-            gpsIfd = new IfdData(IfdId.TYPE_IFD_GPS);
-            addIfdData(gpsIfd);
-        }
-        ExifTag latTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE, ExifTag.TYPE_RATIONAL,
-                3, IfdId.TYPE_IFD_GPS);
-        ExifTag longTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE, ExifTag.TYPE_RATIONAL,
-                3, IfdId.TYPE_IFD_GPS);
-        ExifTag latRefTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE_REF,
-                ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
-        ExifTag longRefTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE_REF,
-                ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
-        latTag.setValue(toExifLatLong(latitude));
-        longTag.setValue(toExifLatLong(longitude));
-        latRefTag.setValue(latitude >= 0
-                ? ExifTag.GpsLatitudeRef.NORTH
-                : ExifTag.GpsLatitudeRef.SOUTH);
-        longRefTag.setValue(longitude >= 0
-                ? ExifTag.GpsLongitudeRef.EAST
-                : ExifTag.GpsLongitudeRef.WEST);
-        gpsIfd.setTag(latTag);
-        gpsIfd.setTag(longTag);
-        gpsIfd.setTag(latRefTag);
-        gpsIfd.setTag(longRefTag);
+    protected void addIfdData(IfdData data) {
+        mIfdDatas[data.getId()] = data;
     }
 
-    private static Rational[] toExifLatLong(double value) {
-        // convert to the format dd/1 mm/1 ssss/100
-        value = Math.abs(value);
-        int degrees = (int) value;
-        value = (value - degrees) * 60;
-        int minutes = (int) value;
-        value = (value - minutes) * 6000;
-        int seconds = (int) value;
-        return new Rational[] {
-                new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)};
-    }
-
-    private IfdData getOrCreateIfdData(int ifdId) {
+    /**
+     * Returns the {@link IfdData} object corresponding to a given IFD or
+     * generates one if none exist.
+     */
+    protected IfdData getOrCreateIfdData(int ifdId) {
         IfdData ifdData = mIfdDatas[ifdId];
         if (ifdData == null) {
             ifdData = new IfdData(ifdId);
@@ -189,73 +153,196 @@
     }
 
     /**
-     * Gets the tag with the given tag ID. Returns null if the tag does not exist. For tags
-     * related to interoperability or thumbnail, call {@link #getInteroperabilityTag(short)} and
-     * {@link #getThumbnailTag(short)} respectively.
+     * Returns the tag with a given TID in the given IFD if the tag exists.
+     * Otherwise returns null.
      */
-    public ExifTag getTag(short tagId) {
-        int ifdId = ExifTag.getIfdIdFromTagId(tagId);
-        IfdData ifdData = mIfdDatas[ifdId];
-        return (ifdData == null) ? null : ifdData.getTag(tagId);
+    protected ExifTag getTag(short tag, int ifd) {
+        IfdData ifdData = mIfdDatas[ifd];
+        return (ifdData == null) ? null : ifdData.getTag(tag);
     }
 
     /**
-     * Gets the thumbnail-related tag with the given tag ID.
+     * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
      */
-    public ExifTag getThumbnailTag(short tagId) {
-        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_1];
-        return (ifdData == null) ? null : ifdData.getTag(tagId);
+    protected ExifTag addTag(ExifTag tag) {
+        if (tag != null) {
+            int ifd = tag.getIfd();
+            return addTag(tag, ifd);
+        }
+        return null;
     }
 
     /**
-     * Gets the interoperability-related tag with the given tag ID.
+     * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+     * with the same TID or null if none exist.
      */
-    public ExifTag getInteroperabilityTag(short tagId) {
-        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY];
-        return (ifdData == null) ? null : ifdData.getTag(tagId);
+    protected ExifTag addTag(ExifTag tag, int ifdId) {
+        if (tag != null && ExifTag.isValidIfd(ifdId)) {
+            IfdData ifdData = getOrCreateIfdData(ifdId);
+            return ifdData.setTag(tag);
+        }
+        return null;
     }
 
-    /**
-     * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags
-     * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or
-     * {@link #addThumbnailTag(short)} respectively.
-     * @exception IllegalArgumentException if the tag ID is invalid.
-     */
-    public ExifTag addTag(short tagId) {
-        int ifdId = ExifTag.getIfdIdFromTagId(tagId);
-        IfdData ifdData = getOrCreateIfdData(ifdId);
-        ExifTag tag = ExifTag.buildTag(tagId);
-        ifdData.setTag(tag);
-        return tag;
-    }
-
-    /**
-     * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced
-     * by the new tag.
-     * @exception IllegalArgumentException if the tag ID is invalid.
-     */
-    public ExifTag addThumbnailTag(short tagId) {
-        IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1);
-        ExifTag tag = ExifTag.buildThumbnailTag(tagId);
-        ifdData.setTag(tag);
-        return tag;
-    }
-
-    /**
-     * Adds an interoperability-related tag with the given tag ID. The original tag will be
-     * replaced by the new tag.
-     * @exception IllegalArgumentException if the tag ID is invalid.
-     */
-    public ExifTag addInteroperabilityTag(short tagId) {
-        IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
-        ExifTag tag = ExifTag.buildInteroperabilityTag(tagId);
-        ifdData.setTag(tag);
-        return tag;
-    }
-
-    public void removeThumbnailData() {
+    protected void clearThumbnailAndStrips() {
         mThumbnail = null;
         mStripBytes.clear();
+    }
+
+    /**
+     * Removes the thumbnail and its related tags. IFD1 will be removed.
+     */
+    protected void removeThumbnailData() {
+        clearThumbnailAndStrips();
         mIfdDatas[IfdId.TYPE_IFD_1] = null;
     }
-}
\ No newline at end of file
+
+    /**
+     * Removes the tag with a given TID and IFD.
+     */
+    protected void removeTag(short tagId, int ifdId) {
+        IfdData ifdData = mIfdDatas[ifdId];
+        if (ifdData == null) {
+            return;
+        }
+        ifdData.removeTag(tagId);
+    }
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    protected String getUserComment() {
+        IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
+        if (ifdData == null) {
+            return null;
+        }
+        ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+        if (tag == null) {
+            return null;
+        }
+        if (tag.getComponentCount() < 8) {
+            return null;
+        }
+
+        byte[] buf = new byte[tag.getComponentCount()];
+        tag.getBytes(buf);
+
+        byte[] code = new byte[8];
+        System.arraycopy(buf, 0, code, 0, 8);
+
+        try {
+            if (Arrays.equals(code, USER_COMMENT_ASCII)) {
+                return new String(buf, 8, buf.length - 8, "US-ASCII");
+            } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
+                return new String(buf, 8, buf.length - 8, "EUC-JP");
+            } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
+                return new String(buf, 8, buf.length - 8, "UTF-16");
+            } else {
+                return null;
+            }
+        } catch (UnsupportedEncodingException e) {
+            Log.w(TAG, "Failed to decode the user comment");
+            return null;
+        }
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTags() {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag[] tags = d.getAllTags();
+                if (tags != null) {
+                    for (ExifTag t : tags) {
+                        ret.add(t);
+                    }
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForIfd(int ifd) {
+        IfdData d = mIfdDatas[ifd];
+        if (d == null) {
+            return null;
+        }
+        ExifTag[] tags = d.getAllTags();
+        if (tags == null) {
+            return null;
+        }
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+        for (ExifTag t : tags) {
+            ret.add(t);
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a list of all {@link ExifTag}s with a given TID or null if there
+     * are none.
+     */
+    protected List<ExifTag> getAllTagsForTagId(short tag) {
+        ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+        for (IfdData d : mIfdDatas) {
+            if (d != null) {
+                ExifTag t = d.getTag(tag);
+                if (t != null) {
+                    ret.add(t);
+                }
+            }
+        }
+        if (ret.size() == 0) {
+            return null;
+        }
+        return ret;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof ExifData) {
+            ExifData data = (ExifData) obj;
+            if (data.mByteOrder != mByteOrder ||
+                    data.mStripBytes.size() != mStripBytes.size() ||
+                    !Arrays.equals(data.mThumbnail, mThumbnail)) {
+                return false;
+            }
+            for (int i = 0; i < mStripBytes.size(); i++) {
+                if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+                    return false;
+                }
+            }
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                IfdData ifd1 = data.getIfdData(i);
+                IfdData ifd2 = getIfdData(i);
+                if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
new file mode 100644
index 0000000..a1cf0fc
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
@@ -0,0 +1,2407 @@
+/*
+ * 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.gallery3d.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+    public static final int TAG_NULL = -1;
+    public static final int IFD_NULL = -1;
+    public static final int DEFINITION_NULL = 0;
+
+    /**
+     * Tag constants for Jeita EXIF 2.2
+     */
+
+    // IFD 0
+    public static final int TAG_IMAGE_WIDTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+    public static final int TAG_IMAGE_LENGTH =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+    public static final int TAG_BITS_PER_SAMPLE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+    public static final int TAG_COMPRESSION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+    public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+    public static final int TAG_IMAGE_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+    public static final int TAG_MAKE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+    public static final int TAG_MODEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+    public static final int TAG_STRIP_OFFSETS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+    public static final int TAG_ORIENTATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+    public static final int TAG_SAMPLES_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+    public static final int TAG_ROWS_PER_STRIP =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+    public static final int TAG_STRIP_BYTE_COUNTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+    public static final int TAG_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+    public static final int TAG_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+    public static final int TAG_PLANAR_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+    public static final int TAG_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+    public static final int TAG_TRANSFER_FUNCTION =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+    public static final int TAG_SOFTWARE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+    public static final int TAG_DATE_TIME =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+    public static final int TAG_ARTIST =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+    public static final int TAG_WHITE_POINT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+    public static final int TAG_PRIMARY_CHROMATICITIES =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+    public static final int TAG_Y_CB_CR_COEFFICIENTS =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+    public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+    public static final int TAG_Y_CB_CR_POSITIONING =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+    public static final int TAG_REFERENCE_BLACK_WHITE =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+    public static final int TAG_COPYRIGHT =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+    public static final int TAG_EXIF_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+    public static final int TAG_GPS_IFD =
+        defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+    // IFD 1
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+        defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+    // IFD Exif Tags
+    public static final int TAG_EXPOSURE_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+    public static final int TAG_F_NUMBER =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+    public static final int TAG_EXPOSURE_PROGRAM =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+    public static final int TAG_SPECTRAL_SENSITIVITY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+    public static final int TAG_ISO_SPEED_RATINGS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+    public static final int TAG_OECF =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+    public static final int TAG_EXIF_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+    public static final int TAG_DATE_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+    public static final int TAG_DATE_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+    public static final int TAG_COMPONENTS_CONFIGURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+    public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+    public static final int TAG_SHUTTER_SPEED_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+    public static final int TAG_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+    public static final int TAG_BRIGHTNESS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+    public static final int TAG_EXPOSURE_BIAS_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+    public static final int TAG_MAX_APERTURE_VALUE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+    public static final int TAG_SUBJECT_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+    public static final int TAG_METERING_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+    public static final int TAG_LIGHT_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+    public static final int TAG_FLASH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+    public static final int TAG_FOCAL_LENGTH =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+    public static final int TAG_SUBJECT_AREA =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+    public static final int TAG_MAKER_NOTE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+    public static final int TAG_USER_COMMENT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+    public static final int TAG_SUB_SEC_TIME =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+    public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+    public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+    public static final int TAG_FLASHPIX_VERSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+    public static final int TAG_COLOR_SPACE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+    public static final int TAG_PIXEL_X_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+    public static final int TAG_PIXEL_Y_DIMENSION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+    public static final int TAG_RELATED_SOUND_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+    public static final int TAG_INTEROPERABILITY_IFD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+    public static final int TAG_FLASH_ENERGY =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+    public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+    public static final int TAG_SUBJECT_LOCATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+    public static final int TAG_EXPOSURE_INDEX =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+    public static final int TAG_SENSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+    public static final int TAG_FILE_SOURCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+    public static final int TAG_SCENE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+    public static final int TAG_CFA_PATTERN =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+    public static final int TAG_CUSTOM_RENDERED =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+    public static final int TAG_EXPOSURE_MODE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+    public static final int TAG_WHITE_BALANCE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+    public static final int TAG_DIGITAL_ZOOM_RATIO =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+    public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+    public static final int TAG_SCENE_CAPTURE_TYPE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+    public static final int TAG_GAIN_CONTROL =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+    public static final int TAG_CONTRAST =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+    public static final int TAG_SATURATION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+    public static final int TAG_SHARPNESS =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+    public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+    public static final int TAG_SUBJECT_DISTANCE_RANGE =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+    public static final int TAG_IMAGE_UNIQUE_ID =
+        defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+    // IFD GPS tags
+    public static final int TAG_GPS_VERSION_ID =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+    public static final int TAG_GPS_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+    public static final int TAG_GPS_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+    public static final int TAG_GPS_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+    public static final int TAG_GPS_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+    public static final int TAG_GPS_ALTITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+    public static final int TAG_GPS_ALTITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+    public static final int TAG_GPS_TIME_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+    public static final int TAG_GPS_SATTELLITES =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+    public static final int TAG_GPS_STATUS =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+    public static final int TAG_GPS_MEASURE_MODE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+    public static final int TAG_GPS_DOP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+    public static final int TAG_GPS_SPEED_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+    public static final int TAG_GPS_SPEED =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+    public static final int TAG_GPS_TRACK_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+    public static final int TAG_GPS_TRACK =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+    public static final int TAG_GPS_IMG_DIRECTION_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+    public static final int TAG_GPS_IMG_DIRECTION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+    public static final int TAG_GPS_MAP_DATUM =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+    public static final int TAG_GPS_DEST_LATITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+    public static final int TAG_GPS_DEST_LATITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+    public static final int TAG_GPS_DEST_LONGITUDE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+    public static final int TAG_GPS_DEST_LONGITUDE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+    public static final int TAG_GPS_DEST_BEARING_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+    public static final int TAG_GPS_DEST_BEARING =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+    public static final int TAG_GPS_DEST_DISTANCE_REF =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+    public static final int TAG_GPS_DEST_DISTANCE =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+    public static final int TAG_GPS_PROCESSING_METHOD =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+    public static final int TAG_GPS_AREA_INFORMATION =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+    public static final int TAG_GPS_DATE_STAMP =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+    public static final int TAG_GPS_DIFFERENTIAL =
+        defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+    // IFD Interoperability tags
+    public static final int TAG_INTEROPERABILITY_INDEX =
+        defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+    /**
+     * Tags that contain offset markers. These are included in the banned
+     * defines.
+     */
+    private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+    static {
+        sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+        sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+        sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+    }
+
+    /**
+     * Tags with definitions that cannot be overridden (banned defines).
+     */
+    protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+    static {
+        sBannedDefines.add(getTrueTagKey(TAG_NULL));
+        sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+    }
+
+    /**
+     * Returns the constant representing a tag with a given TID and default IFD.
+     */
+    public static int defineTag(int ifdId, short tagId) {
+        return (tagId & 0x0000ffff) | (ifdId << 16);
+    }
+
+    /**
+     * Returns the TID for a tag constant.
+     */
+    public static short getTrueTagKey(int tag) {
+        // Truncate
+        return (short) tag;
+    }
+
+    /**
+     * Returns the default IFD for a tag constant.
+     */
+    public static int getTrueIfd(int tag) {
+        return tag >>> 16;
+    }
+
+    /**
+     * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+     * follows:
+     * <ul>
+     * <li>TOP_LEFT is the normal orientation.</li>
+     * <li>TOP_RIGHT is a left-right mirror.</li>
+     * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+     * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+     * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+     * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+     * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+     * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+     * </ul>
+     */
+    public static interface Orientation {
+        public static final short TOP_LEFT = 1;
+        public static final short TOP_RIGHT = 2;
+        public static final short BOTTOM_LEFT = 3;
+        public static final short BOTTOM_RIGHT = 4;
+        public static final short LEFT_TOP = 5;
+        public static final short RIGHT_TOP = 6;
+        public static final short LEFT_BOTTOM = 7;
+        public static final short RIGHT_BOTTOM = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+     */
+    public static interface YCbCrPositioning {
+        public static final short CENTERED = 1;
+        public static final short CO_SITED = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPRESSION}
+     */
+    public static interface Compression {
+        public static final short UNCOMPRESSION = 1;
+        public static final short JPEG = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_RESOLUTION_UNIT}
+     */
+    public static interface ResolutionUnit {
+        public static final short INCHES = 2;
+        public static final short CENTIMETERS = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+     */
+    public static interface PhotometricInterpretation {
+        public static final short RGB = 2;
+        public static final short YCBCR = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_PLANAR_CONFIGURATION}
+     */
+    public static interface PlanarConfiguration {
+        public static final short CHUNKY = 1;
+        public static final short PLANAR = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_PROGRAM}
+     */
+    public static interface ExposureProgram {
+        public static final short NOT_DEFINED = 0;
+        public static final short MANUAL = 1;
+        public static final short NORMAL_PROGRAM = 2;
+        public static final short APERTURE_PRIORITY = 3;
+        public static final short SHUTTER_PRIORITY = 4;
+        public static final short CREATIVE_PROGRAM = 5;
+        public static final short ACTION_PROGRAM = 6;
+        public static final short PROTRAIT_MODE = 7;
+        public static final short LANDSCAPE_MODE = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_METERING_MODE}
+     */
+    public static interface MeteringMode {
+        public static final short UNKNOWN = 0;
+        public static final short AVERAGE = 1;
+        public static final short CENTER_WEIGHTED_AVERAGE = 2;
+        public static final short SPOT = 3;
+        public static final short MULTISPOT = 4;
+        public static final short PATTERN = 5;
+        public static final short PARTAIL = 6;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+     * standard, we can treat this constant as bitwise flag.
+     * <p>
+     * e.g.
+     * <p>
+     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+     * MODE_AUTO_MODE
+     */
+    public static interface Flash {
+        // LSB
+        public static final short DID_NOT_FIRED = 0;
+        public static final short FIRED = 1;
+        // 1st~2nd bits
+        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
+        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
+        // 3rd~4th bits
+        public static final short MODE_UNKNOWN = 0 << 3;
+        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
+        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
+        public static final short MODE_AUTO_MODE = 3 << 3;
+        // 5th bit
+        public static final short FUNCTION_PRESENT = 0 << 5;
+        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+        // 6th bit
+        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+    }
+
+    /**
+     * Constants for {@link TAG_COLOR_SPACE}
+     */
+    public static interface ColorSpace {
+        public static final short SRGB = 1;
+        public static final short UNCALIBRATED = (short) 0xFFFF;
+    }
+
+    /**
+     * Constants for {@link TAG_EXPOSURE_MODE}
+     */
+    public static interface ExposureMode {
+        public static final short AUTO_EXPOSURE = 0;
+        public static final short MANUAL_EXPOSURE = 1;
+        public static final short AUTO_BRACKET = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_WHITE_BALANCE}
+     */
+    public static interface WhiteBalance {
+        public static final short AUTO = 0;
+        public static final short MANUAL = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+     */
+    public static interface SceneCapture {
+        public static final short STANDARD = 0;
+        public static final short LANDSCAPE = 1;
+        public static final short PROTRAIT = 2;
+        public static final short NIGHT_SCENE = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+     */
+    public static interface ComponentsConfiguration {
+        public static final short NOT_EXIST = 0;
+        public static final short Y = 1;
+        public static final short CB = 2;
+        public static final short CR = 3;
+        public static final short R = 4;
+        public static final short G = 5;
+        public static final short B = 6;
+    }
+
+    /**
+     * Constants for {@link TAG_LIGHT_SOURCE}
+     */
+    public static interface LightSource {
+        public static final short UNKNOWN = 0;
+        public static final short DAYLIGHT = 1;
+        public static final short FLUORESCENT = 2;
+        public static final short TUNGSTEN = 3;
+        public static final short FLASH = 4;
+        public static final short FINE_WEATHER = 9;
+        public static final short CLOUDY_WEATHER = 10;
+        public static final short SHADE = 11;
+        public static final short DAYLIGHT_FLUORESCENT = 12;
+        public static final short DAY_WHITE_FLUORESCENT = 13;
+        public static final short COOL_WHITE_FLUORESCENT = 14;
+        public static final short WHITE_FLUORESCENT = 15;
+        public static final short STANDARD_LIGHT_A = 17;
+        public static final short STANDARD_LIGHT_B = 18;
+        public static final short STANDARD_LIGHT_C = 19;
+        public static final short D55 = 20;
+        public static final short D65 = 21;
+        public static final short D75 = 22;
+        public static final short D50 = 23;
+        public static final short ISO_STUDIO_TUNGSTEN = 24;
+        public static final short OTHER = 255;
+    }
+
+    /**
+     * Constants for {@link TAG_SENSING_METHOD}
+     */
+    public static interface SensingMethod {
+        public static final short NOT_DEFINED = 1;
+        public static final short ONE_CHIP_COLOR = 2;
+        public static final short TWO_CHIP_COLOR = 3;
+        public static final short THREE_CHIP_COLOR = 4;
+        public static final short COLOR_SEQUENTIAL_AREA = 5;
+        public static final short TRILINEAR = 7;
+        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+    }
+
+    /**
+     * Constants for {@link TAG_FILE_SOURCE}
+     */
+    public static interface FileSource {
+        public static final short DSC = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_SCENE_TYPE}
+     */
+    public static interface SceneType {
+        public static final short DIRECT_PHOTOGRAPHED = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GAIN_CONTROL}
+     */
+    public static interface GainControl {
+        public static final short NONE = 0;
+        public static final short LOW_UP = 1;
+        public static final short HIGH_UP = 2;
+        public static final short LOW_DOWN = 3;
+        public static final short HIGH_DOWN = 4;
+    }
+
+    /**
+     * Constants for {@link TAG_CONTRAST}
+     */
+    public static interface Contrast {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SATURATION}
+     */
+    public static interface Saturation {
+        public static final short NORMAL = 0;
+        public static final short LOW = 1;
+        public static final short HIGH = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SHARPNESS}
+     */
+    public static interface Sharpness {
+        public static final short NORMAL = 0;
+        public static final short SOFT = 1;
+        public static final short HARD = 2;
+    }
+
+    /**
+     * Constants for {@link TAG_SUBJECT_DISTANCE}
+     */
+    public static interface SubjectDistance {
+        public static final short UNKNOWN = 0;
+        public static final short MACRO = 1;
+        public static final short CLOSE_VIEW = 2;
+        public static final short DISTANT_VIEW = 3;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LATITUDE_REF},
+     * {@link TAG_GPS_DEST_LATITUDE_REF}
+     */
+    public static interface GpsLatitudeRef {
+        public static final String NORTH = "N";
+        public static final String SOUTH = "S";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_LONGITUDE_REF},
+     * {@link TAG_GPS_DEST_LONGITUDE_REF}
+     */
+    public static interface GpsLongitudeRef {
+        public static final String EAST = "E";
+        public static final String WEST = "W";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_ALTITUDE_REF}
+     */
+    public static interface GpsAltitudeRef {
+        public static final short SEA_LEVEL = 0;
+        public static final short SEA_LEVEL_NEGATIVE = 1;
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_STATUS}
+     */
+    public static interface GpsStatus {
+        public static final String IN_PROGRESS = "A";
+        public static final String INTEROPERABILITY = "V";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_MEASURE_MODE}
+     */
+    public static interface GpsMeasureMode {
+        public static final String MODE_2_DIMENSIONAL = "2";
+        public static final String MODE_3_DIMENSIONAL = "3";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_SPEED_REF},
+     * {@link TAG_GPS_DEST_DISTANCE_REF}
+     */
+    public static interface GpsSpeedRef {
+        public static final String KILOMETERS = "K";
+        public static final String MILES = "M";
+        public static final String KNOTS = "N";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_TRACK_REF},
+     * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+     */
+    public static interface GpsTrackRef {
+        public static final String TRUE_DIRECTION = "T";
+        public static final String MAGNETIC_DIRECTION = "M";
+    }
+
+    /**
+     * Constants for {@link TAG_GPS_DIFFERENTIAL}
+     */
+    public static interface GpsDifferential {
+        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+    }
+
+    private static final String NULL_ARGUMENT_STRING = "Argument is null";
+    private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+    public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+    public ExifInterface() {
+        mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+    }
+
+    /**
+     * Reads the exif tags from a byte array, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(byte[] jpeg) throws IOException {
+        readExif(new ByteArrayInputStream(jpeg));
+    }
+
+    /**
+     * Reads the exif tags from an InputStream, clearing this ExifInterface
+     * object's existing exif tags.
+     *
+     * @param inStream an InputStream containing a jpeg compressed image.
+     * @throws IOException
+     */
+    public void readExif(InputStream inStream) throws IOException {
+        if (inStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifData d = null;
+        try {
+            d = new ExifReader(this).read(inStream);
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+        mData = d;
+    }
+
+    /**
+     * Reads the exif tags from a file, clearing this ExifInterface object's
+     * existing exif tags.
+     *
+     * @param inFileName a string representing the filepath to jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void readExif(String inFileName) throws FileNotFoundException, IOException {
+        if (inFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName));
+            readExif(is);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Sets the exif tags, clearing this ExifInterface object's existing exif
+     * tags.
+     *
+     * @param tags a collection of exif tags to set.
+     */
+    public void setExif(Collection<ExifTag> tags) {
+        clearExif();
+        setTags(tags);
+    }
+
+    /**
+     * Clears this ExifInterface object's existing exif tags.
+     */
+    public void clearExif() {
+        mData = new ExifData(DEFAULT_BYTE_ORDER);
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+        if (jpeg == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        s.write(jpeg, 0, jpeg.length);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutStream the OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+        if (bmap == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutStream an OutputStream to which the jpeg image with added
+     *            exif tags will be written.
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+        if (jpegStream == null || exifOutStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = getExifWriterStream(exifOutStream);
+        doExifStreamIO(jpegStream, s);
+        s.flush();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg image,
+     * removing prior exif tags.
+     *
+     * @param jpeg a byte array containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (jpeg == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            s.write(jpeg, 0, jpeg.length);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg compressed
+     * bitmap, removing prior exif tags.
+     *
+     * @param bmap a bitmap to compress and write exif into.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+            IOException {
+        if (bmap == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg stream,
+     * removing prior exif tags.
+     *
+     * @param jpegStream an InputStream containing a jpeg compressed image.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(InputStream jpegStream, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegStream == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream s = null;
+        try {
+            s = getExifWriterStream(exifOutFileName);
+            doExifStreamIO(jpegStream, s);
+            s.flush();
+        } catch (IOException e) {
+            closeSilently(s);
+            throw e;
+        }
+        s.close();
+    }
+
+    /**
+     * Writes the tags from this ExifInterface object into a jpeg file, removing
+     * prior exif tags.
+     *
+     * @param jpegFileName a String containing the filepath for a jpeg file.
+     * @param exifOutFileName a String containing the filepath to which the jpeg
+     *            image with added exif tags will be written.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public void writeExif(String jpegFileName, String exifOutFileName)
+            throws FileNotFoundException, IOException {
+        if (jpegFileName == null || exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        InputStream is = null;
+        try {
+            is = new FileInputStream(jpegFileName);
+            writeExif(is, exifOutFileName);
+        } catch (IOException e) {
+            closeSilently(is);
+            throw e;
+        }
+        is.close();
+    }
+
+    /**
+     * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param outStream an OutputStream to wrap.
+     * @return an OutputStream that wraps the outStream parameter, and adds exif
+     *         metadata. A jpeg image should be written to this stream.
+     */
+    public OutputStream getExifWriterStream(OutputStream outStream) {
+        if (outStream == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        ExifOutputStream eos = new ExifOutputStream(outStream, this);
+        eos.setExifData(mData);
+        return eos;
+    }
+
+    /**
+     * Returns an OutputStream object that writes to a file. Exif tags in this
+     * ExifInterface object will be added to a jpeg image written to this
+     * stream, removing prior exif tags. Other methods of this ExifInterface
+     * object should not be called until the returned OutputStream has been
+     * closed.
+     *
+     * @param exifOutFileName an String containing a filepath for a jpeg file.
+     * @return an OutputStream that writes to the exifOutFileName file, and adds
+     *         exif metadata. A jpeg image should be written to this stream.
+     * @throws FileNotFoundException
+     */
+    public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+        if (exifOutFileName == null) {
+            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+        }
+        OutputStream out = null;
+        try {
+            out = (OutputStream) new FileOutputStream(exifOutFileName);
+        } catch (FileNotFoundException e) {
+            closeSilently(out);
+            throw e;
+        }
+        return getExifWriterStream(out);
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a file for the
+     * given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param filename a String containing a filepath for a jpeg file with exif
+     *            tags to rewrite.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     */
+    public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException, IOException {
+        RandomAccessFile file = null;
+        InputStream is = null;
+        boolean ret;
+        try {
+            File temp = new File(filename);
+            is = new BufferedInputStream(new FileInputStream(temp));
+
+            // Parse beginning of APP1 in exif to find size of exif header.
+            ExifParser parser = null;
+            try {
+                parser = ExifParser.parse(is, this);
+            } catch (ExifInvalidFormatException e) {
+                throw new IOException("Invalid exif format : ", e);
+            }
+            long exifSize = parser.getOffsetToExifEndFromSOF();
+
+            // Free up resources
+            is.close();
+            is = null;
+
+            // Open file for memory mapping.
+            file = new RandomAccessFile(temp, "rw");
+            long fileLength = file.length();
+            if (fileLength < exifSize) {
+                throw new IOException("Filesize changed during operation");
+            }
+
+            // Map only exif header into memory.
+            ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+            // Attempt to overwrite tag values without changing lengths (avoids
+            // file copy).
+            ret = rewriteExif(buf, tags);
+        } catch (IOException e) {
+            closeSilently(file);
+            throw e;
+        } finally {
+            closeSilently(is);
+        }
+        file.close();
+        return ret;
+    }
+
+    /**
+     * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+     * the given tags. If tags do not exist or do not have the same size as the
+     * existing exif tags, this method will fail.
+     *
+     * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+     *            rewrite.
+     * @param tags tags that will be written into the jpeg ByteBuffer over
+     *            existing tags if possible.
+     * @return true if success, false if could not overwrite. If false, no
+     *         changes are made to the ByteBuffer.
+     * @throws IOException
+     */
+    public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+        ExifModifier mod = null;
+        try {
+            mod = new ExifModifier(buf, this);
+            for (ExifTag t : tags) {
+                mod.modifyTag(t);
+            }
+            return mod.commit();
+        } catch (ExifInvalidFormatException e) {
+            throw new IOException("Invalid exif format : " + e);
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+     * fall back to overwriting file. This preserves tags that are not being
+     * rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @param tags tags that will be written into the jpeg file over existing
+     *            tags if possible.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+            throws FileNotFoundException,
+            IOException {
+        // Attempt in-place write
+        if (!rewriteExif(filename, tags)) {
+            // Fall back to doing a copy
+            ExifData tempData = mData;
+            mData = new ExifData(DEFAULT_BYTE_ORDER);
+            FileInputStream is = null;
+            ByteArrayOutputStream bytes = null;
+            try {
+                is = new FileInputStream(filename);
+                bytes = new ByteArrayOutputStream();
+                doExifStreamIO(is, bytes);
+                byte[] imageBytes = bytes.toByteArray();
+                readExif(imageBytes);
+                setTags(tags);
+                writeExif(imageBytes, filename);
+            } catch (IOException e) {
+                closeSilently(is);
+                throw e;
+            } finally {
+                is.close();
+                // Prevent clobbering of mData
+                mData = tempData;
+            }
+        }
+    }
+
+    /**
+     * Attempts to do an in-place rewrite of the exif metadata using the tags in
+     * this ExifInterface object. If this fails, fall back to overwriting file.
+     * This preserves tags that are not being rewritten.
+     *
+     * @param filename a String containing a filepath for a jpeg file.
+     * @throws FileNotFoundException
+     * @throws IOException
+     * @see #rewriteExif
+     */
+    public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+        forceRewriteExif(filename, getAllTags());
+    }
+
+    /**
+     * Get the exif tags in this ExifInterface object or null if none exist.
+     *
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getAllTags() {
+        return mData.getAllTags();
+    }
+
+    /**
+     * Returns a list of ExifTags that share a TID (which can be obtained by
+     * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param tagId a TID as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForTagId(short tagId) {
+        return mData.getAllTagsForTagId(tagId);
+    }
+
+    /**
+     * Returns a list of ExifTags that share an IFD (which can be obtained by
+     * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+     * exist.
+     *
+     * @param ifdId an IFD as defined in the exif standard (or with
+     *            {@link #defineTag}).
+     * @return a List of {@link ExifTag}s.
+     */
+    public List<ExifTag> getTagsForIfdId(int ifdId) {
+        return mData.getAllTagsForIfd(ifdId);
+    }
+
+    /**
+     * Gets an ExifTag for an IFD other than the tag's default.
+     *
+     * @see #getTag
+     */
+    public ExifTag getTag(int tagId, int ifdId) {
+        if (!ExifTag.isValidIfd(ifdId)) {
+            return null;
+        }
+        return mData.getTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Returns the ExifTag in that tag's default IFD for a defined tag constant
+     * or null if none exists.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return an {@link ExifTag} or null if none exists.
+     */
+    public ExifTag getTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTag(tagId, ifdId);
+    }
+
+    /**
+     * Gets a tag value for an IFD other than the tag's default.
+     *
+     * @see #getTagValue
+     */
+    public Object getTagValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        return (t == null) ? null : t.getValue();
+    }
+
+    /**
+     * Returns the value of the ExifTag in that tag's default IFD for a defined
+     * tag constant or null if none exists or the value could not be cast into
+     * the return type.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the value of the ExifTag or null if none exists.
+     */
+    public Object getTagValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagValue(tagId, ifdId);
+    }
+
+    /*
+     * Getter methods that are similar to getTagValue. Null is returned if the
+     * tag value cannot be cast into the return type.
+     */
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsString();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public String getTagStringValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagStringValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId, int ifdId) {
+        long[] l = getTagLongValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Long(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Long getTagLongValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId, int ifdId) {
+        int[] l = getTagIntValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Integer(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Integer getTagIntValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId, int ifdId) {
+        byte[] l = getTagByteValues(tagId, ifdId);
+        if (l == null || l.length <= 0) {
+            return null;
+        }
+        return new Byte(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Byte getTagByteValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId, int ifdId) {
+        Rational[] l = getTagRationalValues(tagId, ifdId);
+        if (l == null || l.length == 0) {
+            return null;
+        }
+        return new Rational(l[0]);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational getTagRationalValue(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValue(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsLongs();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public long[] getTagLongValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagLongValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsInts();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public int[] getTagIntValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagIntValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsBytes();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public byte[] getTagByteValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagByteValues(tagId, ifdId);
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return null;
+        }
+        return t.getValueAsRationals();
+    }
+
+    /**
+     * @see #getTagValue
+     */
+    public Rational[] getTagRationalValues(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return getTagRationalValues(tagId, ifdId);
+    }
+
+    /**
+     * Checks whether a tag has a defined number of elements.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return true if the tag has a defined number of elements.
+     */
+    public boolean isTagCountDefined(int tagId) {
+        int info = getTagInfo().get(tagId);
+        // No value in info can be zero, as all tags have a non-zero type
+        if (info == 0) {
+            return false;
+        }
+        return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+    }
+
+    /**
+     * Gets the defined number of elements for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+     *         tag or the number of elements is not defined.
+     */
+    public int getDefinedTagCount(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return ExifTag.SIZE_UNDEFINED;
+        }
+        return getComponentCountFromInfo(info);
+    }
+
+    /**
+     * Gets the number of elements for an ExifTag in a given IFD.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD containing the ExifTag to check.
+     * @return the number of elements in the ExifTag, if the tag's size is
+     *         undefined this will return the actual number of elements that is
+     *         in the ExifTag's value.
+     */
+    public int getActualTagCount(int tagId, int ifdId) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return 0;
+        }
+        return t.getComponentCount();
+    }
+
+    /**
+     * Gets the default IFD for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+     *         definition exists.
+     */
+    public int getDefinedTagDefaultIfd(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == DEFINITION_NULL) {
+            return IFD_NULL;
+        }
+        return getTrueIfd(tagId);
+    }
+
+    /**
+     * Gets the defined type for a tag.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @return the type.
+     * @see ExifTag#getDataType()
+     */
+    public short getDefinedTagType(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return -1;
+        }
+        return getTypeFromInfo(info);
+    }
+
+    /**
+     * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+     * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+     * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+     * <p>
+     * Note: defining tags with these TID's is disallowed.
+     *
+     * @param tag a tag's TID (can be obtained from a defined tag constant with
+     *            {@link #getTrueTagKey}).
+     * @return true if the TID is that of an offset tag.
+     */
+    protected static boolean isOffsetTag(short tag) {
+        return sOffsetTags.contains(tag);
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in a given IFD if that IFD is
+     * allowed for the tag.  This method will fail anytime the appropriate
+     * {@link ExifTag#setValue} for this tag's datatype would fail.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the tag should be in.
+     * @param val the value of the tag to set.
+     * @return an ExifTag object or null if one could not be constructed.
+     * @see #buildTag
+     */
+    public ExifTag buildTag(int tagId, int ifdId, Object val) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0 || val == null) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+            return null;
+        }
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        if (!t.setValue(val)) {
+            return null;
+        }
+        return t;
+    }
+
+    /**
+     * Creates a tag for a defined tag constant in the tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the tag's value.
+     * @return an ExifTag object.
+     */
+    public ExifTag buildTag(int tagId, Object val) {
+        int ifdId = getTrueIfd(tagId);
+        return buildTag(tagId, ifdId, val);
+    }
+
+    protected ExifTag buildUninitializedTag(int tagId) {
+        int info = getTagInfo().get(tagId);
+        if (info == 0) {
+            return null;
+        }
+        short type = getTypeFromInfo(info);
+        int definedCount = getComponentCountFromInfo(info);
+        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+        int ifdId = getTrueIfd(tagId);
+        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+        return t;
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists in the given IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD that the ExifTag is in.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     * @see #setTagValue
+     */
+    public boolean setTagValue(int tagId, int ifdId, Object val) {
+        ExifTag t = getTag(tagId, ifdId);
+        if (t == null) {
+            return false;
+        }
+        return t.setValue(val);
+    }
+
+    /**
+     * Sets the value of an ExifTag if it exists it's default IFD. The value
+     * must be the correct type and length for that ExifTag.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param val the value to set.
+     * @return true if success, false if the ExifTag doesn't exist or the value
+     *         is the wrong type/length.
+     */
+    public boolean setTagValue(int tagId, Object val) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        return setTagValue(tagId, ifdId, val);
+    }
+
+    /**
+     * Puts an ExifTag into this ExifInterface object's tags, removing a
+     * previous ExifTag with the same TID and IFD. The IFD it is put into will
+     * be the one the tag was created with in {@link #buildTag}.
+     *
+     * @param tag an ExifTag to put into this ExifInterface's tags.
+     * @return the previous ExifTag with the same TID and IFD or null if none
+     *         exists.
+     */
+    public ExifTag setTag(ExifTag tag) {
+        return mData.addTag(tag);
+    }
+
+    /**
+     * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+     * previous ExifTags with the same TID and IFDs will be removed.
+     *
+     * @param tags a Collection of ExifTags.
+     * @see #setTag
+     */
+    public void setTags(Collection<ExifTag> tags) {
+        for (ExifTag t : tags) {
+            setTag(t);
+        }
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from the given IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     * @param ifdId the IFD of the ExifTag to remove.
+     */
+    public void deleteTag(int tagId, int ifdId) {
+        mData.removeTag(getTrueTagKey(tagId), ifdId);
+    }
+
+    /**
+     * Removes the ExifTag for a tag constant from that tag's default IFD.
+     *
+     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void deleteTag(int tagId) {
+        int ifdId = getDefinedTagDefaultIfd(tagId);
+        deleteTag(tagId, ifdId);
+    }
+
+    /**
+     * Creates a new tag definition in this ExifInterface object for a given TID
+     * and default IFD. Creating a definition with the same TID and default IFD
+     * as a previous definition will override it.
+     *
+     * @param tagId the TID for the tag.
+     * @param defaultIfd the default IFD for the tag.
+     * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+     * @param defaultComponentCount the number of elements of this tag's type in
+     *            the tags value.
+     * @param allowedIfds the IFD's this tag is allowed to be put in.
+     * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+     *         {@link #TAG_NULL} if the definition could not be made.
+     */
+    public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+            short defaultComponentCount, int[] allowedIfds) {
+        if (sBannedDefines.contains(tagId)) {
+            return TAG_NULL;
+        }
+        if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+            int tagDef = defineTag(defaultIfd, tagId);
+            if (tagDef == TAG_NULL) {
+                return TAG_NULL;
+            }
+            int[] otherDefs = getTagDefinitionsForTagId(tagId);
+            SparseIntArray infos = getTagInfo();
+            // Make sure defaultIfd is in allowedIfds
+            boolean defaultCheck = false;
+            for (int i : allowedIfds) {
+                if (defaultIfd == i) {
+                    defaultCheck = true;
+                }
+                if (!ExifTag.isValidIfd(i)) {
+                    return TAG_NULL;
+                }
+            }
+            if (!defaultCheck) {
+                return TAG_NULL;
+            }
+
+            int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+            // Make sure no identical tags can exist in allowedIfds
+            if (otherDefs != null) {
+                for (int def : otherDefs) {
+                    int tagInfo = infos.get(def);
+                    int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+                    if ((ifdFlags & allowedFlags) != 0) {
+                        return TAG_NULL;
+                    }
+                }
+            }
+            getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+            return tagDef;
+        }
+        return TAG_NULL;
+    }
+
+    protected int getTagDefinition(short tagId, int defaultIfd) {
+        return getTagInfo().get(defineTag(defaultIfd, tagId));
+    }
+
+    protected int[] getTagDefinitionsForTagId(short tagId) {
+        int[] ifds = IfdData.getIfds();
+        int[] defs = new int[ifds.length];
+        int counter = 0;
+        SparseIntArray infos = getTagInfo();
+        for (int i : ifds) {
+            int def = defineTag(i, tagId);
+            if (infos.get(def) != DEFINITION_NULL) {
+                defs[counter++] = def;
+            }
+        }
+        if (counter == 0) {
+            return null;
+        }
+
+        return Arrays.copyOfRange(defs, 0, counter);
+    }
+
+    protected int getTagDefinitionForTag(ExifTag tag) {
+        short type = tag.getDataType();
+        int count = tag.getComponentCount();
+        int ifd = tag.getIfd();
+        return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+    }
+
+    protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+        int[] defs = getTagDefinitionsForTagId(tagId);
+        if (defs == null) {
+            return TAG_NULL;
+        }
+        SparseIntArray infos = getTagInfo();
+        int ret = TAG_NULL;
+        for (int i : defs) {
+            int info = infos.get(i);
+            short def_type = getTypeFromInfo(info);
+            int def_count = getComponentCountFromInfo(info);
+            int[] def_ifds = getAllowedIfdsFromInfo(info);
+            boolean valid_ifd = false;
+            for (int j : def_ifds) {
+                if (j == ifd) {
+                    valid_ifd = true;
+                    break;
+                }
+            }
+            if (valid_ifd && type == def_type
+                    && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
+                ret = i;
+                break;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Removes a tag definition for given defined tag constant.
+     *
+     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+     */
+    public void removeTagDefinition(int tagId) {
+        getTagInfo().delete(tagId);
+    }
+
+    /**
+     * Resets tag definitions to the default ones.
+     */
+    public void resetTagDefinitions() {
+        mTagInfo = null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+     *
+     * @return the thumbnail as a bitmap.
+     */
+    public Bitmap getThumbnailBitmap() {
+        if (mData.hasCompressedThumbnail()) {
+            byte[] thumb = mData.getCompressedThumbnail();
+            return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement uncompressed
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+     * The bytes may either be an uncompressed strip as specified in the exif
+     * standard or a jpeg compressed image.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnailBytes() {
+        if (mData.hasCompressedThumbnail()) {
+            return mData.getCompressedThumbnail();
+        } else if (mData.hasUncompressedStrip()) {
+            // TODO: implement this
+        }
+        return null;
+    }
+
+    /**
+     * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+     *
+     * @return the thumbnail as a byte array.
+     */
+    public byte[] getThumbnail() {
+        return mData.getCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail is compressed.
+     *
+     * @return true if the thumbnail is compressed.
+     */
+    public boolean isThumbnailCompressed() {
+        return mData.hasCompressedThumbnail();
+    }
+
+    /**
+     * Check if thumbnail exists.
+     *
+     * @return true if a compressed thumbnail exists.
+     */
+    public boolean hasThumbnail() {
+        // TODO: add back in uncompressed strip
+        return mData.hasCompressedThumbnail();
+    }
+
+    // TODO: uncompressed thumbnail setters
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a byte array containing a jpeg compressed image.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(byte[] thumb) {
+        mData.clearThumbnailAndStrips();
+        mData.setCompressedThumbnail(thumb);
+        return true;
+    }
+
+    /**
+     * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+     * thumbnail.
+     *
+     * @param thumb a bitmap to compress to a jpeg thumbnail.
+     * @return true if the thumbnail was set.
+     */
+    public boolean setCompressedThumbnail(Bitmap thumb) {
+        ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+        if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+            return false;
+        }
+        return setCompressedThumbnail(thumbnail.toByteArray());
+    }
+
+    /**
+     * Clears the compressed thumbnail if it exists.
+     */
+    public void removeCompressedThumbnail() {
+        mData.setCompressedThumbnail(null);
+    }
+
+    // Convenience methods:
+
+    /**
+     * Decodes the user comment tag into string as specified in the EXIF
+     * standard. Returns null if decoding failed.
+     */
+    public String getUserComment() {
+        return mData.getUserComment();
+    }
+
+    /**
+     * Returns the Orientation ExifTag value for a given number of degrees.
+     *
+     * @param degrees the amount an image is rotated in degrees.
+     */
+    public static short getOrientationValueForRotation(int degrees) {
+        degrees %= 360;
+        if (degrees < 0) {
+            degrees += 360;
+        }
+        if (degrees < 90) {
+            return Orientation.TOP_LEFT; // 0 degrees
+        } else if (degrees < 180) {
+            return Orientation.RIGHT_TOP; // 90 degrees cw
+        } else if (degrees < 270) {
+            return Orientation.BOTTOM_LEFT; // 180 degrees
+        } else {
+            return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+        }
+    }
+
+    /**
+     * Returns the rotation degrees corresponding to an ExifTag Orientation
+     * value.
+     *
+     * @param orientation the ExifTag Orientation value.
+     */
+    public static int getRotationForOrientationValue(short orientation) {
+        switch (orientation) {
+            case Orientation.TOP_LEFT:
+                return 0;
+            case Orientation.RIGHT_TOP:
+                return 90;
+            case Orientation.BOTTOM_LEFT:
+                return 180;
+            case Orientation.RIGHT_BOTTOM:
+                return 270;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Gets the double representation of the GPS latitude or longitude
+     * coordinate.
+     *
+     * @param coordinate an array of 3 Rationals representing the degrees,
+     *            minutes, and seconds of the GPS location as defined in the
+     *            exif specification.
+     * @param reference a GPS reference reperesented by a String containing "N",
+     *            "S", "E", or "W".
+     * @return the GPS coordinate represented as degrees + minutes/60 +
+     *         seconds/3600
+     */
+    public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+        try {
+            double degrees = coordinate[0].toDouble();
+            double minutes = coordinate[1].toDouble();
+            double seconds = coordinate[2].toDouble();
+            double result = degrees + minutes / 60.0 + seconds / 3600.0;
+            if ((reference.equals("S") || reference.equals("W"))) {
+                return -result;
+            }
+            return result;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Gets the GPS latitude and longitude as a pair of doubles from this
+     * ExifInterface object's tags, or null if the necessary tags do not exist.
+     *
+     * @return an array of 2 doubles containing the latitude, and longitude
+     *         respectively.
+     * @see #convertLatOrLongToDouble
+     */
+    public double[] getLatLongAsDoubles() {
+        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+        if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+                || latitude.length < 3 || longitude.length < 3) {
+            return null;
+        }
+        double[] latLon = new double[2];
+        latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+        latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+        return latLon;
+    }
+
+    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+    private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+    private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+    private final Calendar mGPSTimeStampCalendar = Calendar
+            .getInstance(TimeZone.getTimeZone("UTC"));
+
+    /**
+     * Creates, formats, and sets the DateTimeStamp tag for one of:
+     * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+     * {@link #TAG_DATE_TIME_ORIGINAL}.
+     *
+     * @param tagId one of the DateTimeStamp tags.
+     * @param timestamp a timestamp to format.
+     * @param timezone a TimeZone object.
+     * @return true if success, false if the tag could not be set.
+     */
+    public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+        if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+                || tagId == TAG_DATE_TIME_ORIGINAL) {
+            mDateTimeStampFormat.setTimeZone(timezone);
+            ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+            if (t == null) {
+                return false;
+            }
+            setTag(t);
+        } else {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Creates and sets all to the GPS tags for a give latitude and longitude.
+     *
+     * @param latitude a GPS latitude coordinate.
+     * @param longitude a GPS longitude coordinate.
+     * @return true if success, false if they could not be created or set.
+     */
+    public boolean addGpsTags(double latitude, double longitude) {
+        ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+        ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+        ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+                latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
+                        : ExifInterface.GpsLatitudeRef.SOUTH);
+        ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+                longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
+                        : ExifInterface.GpsLongitudeRef.WEST);
+        if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+            return false;
+        }
+        setTag(latTag);
+        setTag(longTag);
+        setTag(latRefTag);
+        setTag(longRefTag);
+        return true;
+    }
+
+    /**
+     * Creates and sets the GPS timestamp tag.
+     *
+     * @param timestamp a GPS timestamp.
+     * @return true if success, false if could not be created or set.
+     */
+    public boolean addGpsDateTimeStampTag(long timestamp) {
+        ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+        t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+                new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+                new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+        });
+        if (t == null) {
+            return false;
+        }
+        setTag(t);
+        return true;
+    }
+
+    private static Rational[] toExifLatLong(double value) {
+        // convert to the format dd/1 mm/1 ssss/100
+        value = Math.abs(value);
+        int degrees = (int) value;
+        value = (value - degrees) * 60;
+        int minutes = (int) value;
+        value = (value - minutes) * 6000;
+        int seconds = (int) value;
+        return new Rational[] {
+                new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+        };
+    }
+
+    private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+        byte[] buf = new byte[1024];
+        int ret = is.read(buf, 0, 1024);
+        while (ret != -1) {
+            os.write(buf, 0, ret);
+            ret = is.read(buf, 0, 1024);
+        }
+    }
+
+    protected static void closeSilently(Closeable c) {
+        if (c != null) {
+            try {
+                c.close();
+            } catch (Throwable e) {
+                // ignored
+            }
+        }
+    }
+
+    private SparseIntArray mTagInfo = null;
+
+    protected SparseIntArray getTagInfo() {
+        if (mTagInfo == null) {
+            mTagInfo = new SparseIntArray();
+            initTagInfo();
+        }
+        return mTagInfo;
+    }
+
+    private void initTagInfo() {
+        /**
+         * We put tag information in a 4-bytes integer. The first byte a bitmask
+         * representing the allowed IFDs of the tag, the second byte is the data
+         * type, and the last two byte are a short value indicating the default
+         * component count of this tag.
+         */
+        // IFD0 tags
+        int[] ifdAllowedIfds = {
+                IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+        };
+        int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+                | 1);
+        mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+        mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MAKE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_MODEL,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ARTIST,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+                ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+                ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // IFD1 tags
+        int[] ifd1AllowedIfds = {
+            IfdId.TYPE_IFD_1
+        };
+        int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+                ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // Exif tags
+        int[] exifAllowedIfds = {
+            IfdId.TYPE_IFD_EXIF
+        };
+        int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+                exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_OECF,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+                exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FLASH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+                exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_CONTRAST,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SATURATION,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+                exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+                exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+                | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+        // GPS tag
+        int[] gpsAllowedIfds = {
+            IfdId.TYPE_IFD_GPS
+        };
+        int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+        mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+                gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+        mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+        mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+                gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+        mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+                gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+        mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+                gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+        // Interoperability tag
+        int[] interopAllowedIfds = {
+            IfdId.TYPE_IFD_INTEROPERABILITY
+        };
+        int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+        mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+                | ExifTag.SIZE_UNDEFINED);
+    }
+
+    protected static int getAllowedIfdFlagsFromInfo(int info) {
+        return info >>> 24;
+    }
+
+    protected static int[] getAllowedIfdsFromInfo(int info) {
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        int[] ifds = IfdData.getIfds();
+        ArrayList<Integer> l = new ArrayList<Integer>();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            int flag = (ifdFlags >> i) & 1;
+            if (flag == 1) {
+                l.add(ifds[i]);
+            }
+        }
+        if (l.size() <= 0) {
+            return null;
+        }
+        int[] ret = new int[l.size()];
+        int j = 0;
+        for (int i : l) {
+            ret[j++] = i;
+        }
+        return ret;
+    }
+
+    protected static boolean isIfdAllowed(int info, int ifd) {
+        int[] ifds = IfdData.getIfds();
+        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+        for (int i = 0; i < ifds.length; i++) {
+            if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+        if (allowedIfds == null || allowedIfds.length == 0) {
+            return 0;
+        }
+        int flags = 0;
+        int[] ifds = IfdData.getIfds();
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            for (int j : allowedIfds) {
+                if (ifds[i] == j) {
+                    flags |= 1 << i;
+                    break;
+                }
+            }
+        }
+        return flags;
+    }
+
+    protected static short getTypeFromInfo(int info) {
+        return (short) ((info >> 16) & 0x0ff);
+    }
+
+    protected static int getComponentCountFromInfo(int info) {
+        return info & 0x0ffff;
+    }
+
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java
new file mode 100644
index 0000000..f00362b
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.List;
+
+class ExifModifier {
+    public static final String TAG = "ExifModifier";
+    public static final boolean DEBUG = false;
+    private final ByteBuffer mByteBuffer;
+    private final ExifData mTagToModified;
+    private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+    private final ExifInterface mInterface;
+    private int mOffsetBase;
+
+    private static class TagOffset {
+        final int mOffset;
+        final ExifTag mTag;
+
+        TagOffset(ExifTag tag, int offset) {
+            mTag = tag;
+            mOffset = offset;
+        }
+    }
+
+    protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+            ExifInvalidFormatException {
+        mByteBuffer = byteBuffer;
+        mOffsetBase = byteBuffer.position();
+        mInterface = iRef;
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(byteBuffer);
+            // Do not require any IFD;
+            ExifParser parser = ExifParser.parse(is, mInterface);
+            mTagToModified = new ExifData(parser.getByteOrder());
+            mOffsetBase += parser.getTiffStartPosition();
+            mByteBuffer.position(0);
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+    }
+
+    protected ByteOrder getByteOrder() {
+        return mTagToModified.getByteOrder();
+    }
+
+    protected boolean commit() throws IOException, ExifInvalidFormatException {
+        InputStream is = null;
+        try {
+            is = new ByteBufferInputStream(mByteBuffer);
+            int flag = 0;
+            IfdData[] ifdDatas = new IfdData[] {
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
+                    mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
+            };
+
+            if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+                flag |= ExifParser.OPTION_IFD_0;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+                flag |= ExifParser.OPTION_IFD_1;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+                flag |= ExifParser.OPTION_IFD_EXIF;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+                flag |= ExifParser.OPTION_IFD_GPS;
+            }
+            if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
+                flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
+            }
+
+            ExifParser parser = ExifParser.parse(is, flag, mInterface);
+            int event = parser.next();
+            IfdData currIfd = null;
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        currIfd = ifdDatas[parser.getCurrentIfd()];
+                        if (currIfd == null) {
+                            parser.skipRemainingTagsInCurrentIfd();
+                        }
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag oldTag = parser.getTag();
+                        ExifTag newTag = currIfd.getTag(oldTag.getTagId());
+                        if (newTag != null) {
+                            if (newTag.getComponentCount() != oldTag.getComponentCount()
+                                    || newTag.getDataType() != oldTag.getDataType()) {
+                                return false;
+                            } else {
+                                mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
+                                currIfd.removeTag(oldTag.getTagId());
+                                if (currIfd.getTagCount() == 0) {
+                                    parser.skipRemainingTagsInCurrentIfd();
+                                }
+                            }
+                        }
+                        break;
+                }
+                event = parser.next();
+            }
+            for (IfdData ifd : ifdDatas) {
+                if (ifd != null && ifd.getTagCount() > 0) {
+                    return false;
+                }
+            }
+            modify();
+        } finally {
+            ExifInterface.closeSilently(is);
+        }
+        return true;
+    }
+
+    private void modify() {
+        mByteBuffer.order(getByteOrder());
+        for (TagOffset tagOffset : mTagOffsets) {
+            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
+        }
+    }
+
+    private void writeTagValue(ExifTag tag, int offset) {
+        if (DEBUG) {
+            Log.v(TAG, "modifying tag to: \n" + tag.toString());
+            Log.v(TAG, "at offset: " + offset);
+        }
+        mByteBuffer.position(offset + mOffsetBase);
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    mByteBuffer.put(buf);
+                } else {
+                    mByteBuffer.put(buf);
+                    mByteBuffer.put((byte) 0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    Rational v = tag.getRational(i);
+                    mByteBuffer.putInt((int) v.getNumerator());
+                    mByteBuffer.putInt((int) v.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                mByteBuffer.put(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    mByteBuffer.putShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+
+    public void modifyTag(ExifTag tag) {
+        mTagToModified.addTag(tag);
+    }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
index b8db8e3..7ca05f2 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -16,14 +16,51 @@
 
 package com.android.gallery3d.exif;
 
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
+import java.util.ArrayList;
 
-public class ExifOutputStream extends FilterOutputStream {
+/**
+ * This class provides a way to replace the Exif header of a JPEG image.
+ * <p>
+ * Below is an example of writing EXIF data into a file
+ *
+ * <pre>
+ * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
+ *     OutputStream os = null;
+ *     try {
+ *         os = new FileOutputStream(path);
+ *         ExifOutputStream eos = new ExifOutputStream(os);
+ *         // Set the exif header
+ *         eos.setExifData(exif);
+ *         // Write the original jpeg out, the header will be add into the file.
+ *         eos.write(jpeg);
+ *     } catch (FileNotFoundException e) {
+ *         e.printStackTrace();
+ *     } catch (IOException e) {
+ *         e.printStackTrace();
+ *     } finally {
+ *         if (os != null) {
+ *             try {
+ *                 os.close();
+ *             } catch (IOException e) {
+ *                 e.printStackTrace();
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ */
+class ExifOutputStream extends FilterOutputStream {
     private static final String TAG = "ExifOutputStream";
+    private static final boolean DEBUG = false;
+    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
 
     private static final int STATE_SOI = 0;
     private static final int STATE_FRAME_HEADER = 1;
@@ -35,22 +72,33 @@
     private static final short TIFF_LITTLE_ENDIAN = 0x4949;
     private static final short TAG_SIZE = 12;
     private static final short TIFF_HEADER_SIZE = 8;
+    private static final int MAX_EXIF_SIZE = 65535;
 
     private ExifData mExifData;
     private int mState = STATE_SOI;
     private int mByteToSkip;
     private int mByteToCopy;
+    private byte[] mSingleByteArray = new byte[1];
     private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+    private final ExifInterface mInterface;
 
-    public ExifOutputStream(OutputStream ou) {
-        super(ou);
+    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
+        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
+        mInterface = iRef;
     }
 
-    public void setExifData(ExifData exifData) {
+    /**
+     * Sets the ExifData to be written into the JPEG file. Should be called
+     * before writing image data.
+     */
+    protected void setExifData(ExifData exifData) {
         mExifData = exifData;
     }
 
-    public ExifData getExifData() {
+    /**
+     * Gets the Exif header to be written into the JPEF file.
+     */
+    protected ExifData getExifData() {
         return mExifData;
     }
 
@@ -62,9 +110,13 @@
         return byteToRead;
     }
 
+    /**
+     * Writes the image out. The input data should be a valid JPEG format. After
+     * writing, it's Exif header will be replaced by the given header.
+     */
     @Override
     public void write(byte[] buffer, int offset, int length) throws IOException {
-        while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
                 && length > 0) {
             if (mByteToSkip > 0) {
                 int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
@@ -79,22 +131,29 @@
                 mByteToCopy -= byteToProcess;
                 offset += byteToProcess;
             }
-            if (length == 0) return;
+            if (length == 0) {
+                return;
+            }
             switch (mState) {
                 case STATE_SOI:
                     int byteRead = requestByteToBuffer(2, buffer, offset, length);
                     offset += byteRead;
                     length -= byteRead;
-                    if (mBuffer.position() < 2) return;
+                    if (mBuffer.position() < 2) {
+                        return;
+                    }
                     mBuffer.rewind();
-                    assert(mBuffer.getShort() == JpegHeader.SOI);
-                    out.write(mBuffer.array(), 0 ,2);
+                    if (mBuffer.getShort() != JpegHeader.SOI) {
+                        throw new IOException("Not a valid jpeg image, cannot write exif");
+                    }
+                    out.write(mBuffer.array(), 0, 2);
                     mState = STATE_FRAME_HEADER;
                     mBuffer.rewind();
                     writeExifData();
                     break;
                 case STATE_FRAME_HEADER:
-                    // We ignore the APP1 segment and copy all other segments until SOF tag.
+                    // We ignore the APP1 segment and copy all other segments
+                    // until SOF tag.
                     byteRead = requestByteToBuffer(4, buffer, offset, length);
                     offset += byteRead;
                     length -= byteRead;
@@ -106,15 +165,17 @@
                             mBuffer.rewind();
                         }
                     }
-                    if (mBuffer.position() < 4) return;
+                    if (mBuffer.position() < 4) {
+                        return;
+                    }
                     mBuffer.rewind();
                     short marker = mBuffer.getShort();
                     if (marker == JpegHeader.APP1) {
-                        mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
                         mState = STATE_JPEG_DATA;
                     } else if (!JpegHeader.isSofMarker(marker)) {
                         out.write(mBuffer.array(), 0, 4);
-                        mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
                     } else {
                         out.write(mBuffer.array(), 0, 4);
                         mState = STATE_JPEG_DATA;
@@ -127,20 +188,37 @@
         }
     }
 
+    /**
+     * Writes the one bytes out. The input data should be a valid JPEG format.
+     * After writing, it's Exif header will be replaced by the given header.
+     */
     @Override
     public void write(int oneByte) throws IOException {
-        byte[] buf = new byte[] {(byte) (0xff & oneByte)};
-        write(buf);
+        mSingleByteArray[0] = (byte) (0xff & oneByte);
+        write(mSingleByteArray);
     }
 
+    /**
+     * Equivalent to calling write(buffer, 0, buffer.length).
+     */
     @Override
     public void write(byte[] buffer) throws IOException {
         write(buffer, 0, buffer.length);
     }
 
     private void writeExifData() throws IOException {
+        if (mExifData == null) {
+            return;
+        }
+        if (DEBUG) {
+            Log.v(TAG, "Writing exif data...");
+        }
+        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
         createRequiredIfdAndTag();
         int exifSize = calculateAllOffset();
+        if (exifSize + 8 > MAX_EXIF_SIZE) {
+            throw new IOException("Exif header is too large (>64Kb)");
+        }
         OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
         dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
         dataOutputStream.writeShort(JpegHeader.APP1);
@@ -157,6 +235,20 @@
         dataOutputStream.writeInt(8);
         writeAllTags(dataOutputStream);
         writeThumbnail(dataOutputStream);
+        for (ExifTag t : nullTags) {
+            mExifData.addTag(t);
+        }
+    }
+
+    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
+        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
+        for(ExifTag t : data.getAllTags()) {
+            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
+                data.removeTag(t.getTagId(), t.getIfd());
+                nullTags.add(t);
+            }
+        }
+        return nullTags;
     }
 
     private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
@@ -190,71 +282,34 @@
             throws IOException {
         ExifTag[] tags = ifd.getAllTags();
         dataOutputStream.writeShort((short) tags.length);
-        for (ExifTag tag: tags) {
+        for (ExifTag tag : tags) {
             dataOutputStream.writeShort(tag.getTagId());
             dataOutputStream.writeShort(tag.getDataType());
             dataOutputStream.writeInt(tag.getComponentCount());
+            if (DEBUG) {
+                Log.v(TAG, "\n" + tag.toString());
+            }
             if (tag.getDataSize() > 4) {
                 dataOutputStream.writeInt(tag.getOffset());
             } else {
-                writeTagValue(tag, dataOutputStream);
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
                 for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
                     dataOutputStream.write(0);
                 }
             }
         }
         dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
-        for (ExifTag tag: tags) {
+        for (ExifTag tag : tags) {
             if (tag.getDataSize() > 4) {
-                writeTagValue(tag, dataOutputStream);
+                ExifOutputStream.writeTagValue(tag, dataOutputStream);
             }
         }
     }
 
-    private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
-            throws IOException {
-        switch (tag.getDataType()) {
-            case ExifTag.TYPE_ASCII:
-                dataOutputStream.write(tag.getString().getBytes());
-                int remain = tag.getComponentCount() - tag.getString().length();
-                for (int i = 0; i < remain; i++) {
-                    dataOutputStream.write(0);
-                }
-                break;
-            case ExifTag.TYPE_LONG:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeInt(tag.getLong(i));
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-            case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeRational(tag.getRational(i));
-                }
-                break;
-            case ExifTag.TYPE_UNDEFINED:
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-                byte[] buf = new byte[tag.getComponentCount()];
-                tag.getBytes(buf);
-                dataOutputStream.write(buf);
-                break;
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeInt((int) tag.getUnsignedLong(i));
-                }
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT:
-                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
-                    dataOutputStream.writeShort((short) tag.getUnsignedShort(i));
-                }
-                break;
-        }
-    }
-
     private int calculateOffsetOfIfd(IfdData ifd, int offset) {
         offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
         ExifTag[] tags = ifd.getAllTags();
-        for(ExifTag tag: tags) {
+        for (ExifTag tag : tags) {
             if (tag.getDataSize() > 4) {
                 tag.setOffset(offset);
                 offset += tag.getDataSize();
@@ -263,18 +318,21 @@
         return offset;
     }
 
-    private void createRequiredIfdAndTag() {
+    private void createRequiredIfdAndTag() throws IOException {
         // IFD0 is required for all file
         IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
         if (ifd0 == null) {
             ifd0 = new IfdData(IfdId.TYPE_IFD_0);
             mExifData.addIfdData(ifd0);
         }
-        ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD,
-                ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+        if (exifOffsetTag == null) {
+            throw new IOException("No definition for crucial exif tag: "
+                    + ExifInterface.TAG_EXIF_IFD);
+        }
         ifd0.setTag(exifOffsetTag);
 
-        // Exif IFD is required for all file.
+        // Exif IFD is required for all files.
         IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
         if (exifIfd == null) {
             exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
@@ -284,16 +342,23 @@
         // GPS IFD
         IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
         if (gpsIfd != null) {
-            ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD,
-                    ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+            if (gpsOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_GPS_IFD);
+            }
             ifd0.setTag(gpsOffsetTag);
         }
 
         // Interoperability IFD
         IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
         if (interIfd != null) {
-            ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD,
-                    ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF);
+            ExifTag interOffsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+            if (interOffsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
+            }
             exifIfd.setTag(interOffsetTag);
         }
 
@@ -301,27 +366,50 @@
 
         // thumbnail
         if (mExifData.hasCompressedThumbnail()) {
+
             if (ifd1 == null) {
                 ifd1 = new IfdData(IfdId.TYPE_IFD_1);
                 mExifData.addIfdData(ifd1);
             }
-            ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT,
-                    ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+
+            ExifTag offsetTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+            }
+
             ifd1.setTag(offsetTag);
-            ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
-                    ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+            }
+
             lengthTag.setValue(mExifData.getCompressedThumbnail().length);
             ifd1.setTag(lengthTag);
-        } else if (mExifData.hasUncompressedStrip()){
+
+            // Get rid of tags for uncompressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+        } else if (mExifData.hasUncompressedStrip()) {
             if (ifd1 == null) {
                 ifd1 = new IfdData(IfdId.TYPE_IFD_1);
                 mExifData.addIfdData(ifd1);
             }
             int stripCount = mExifData.getStripCount();
-            ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS,
-                    ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
-            ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS,
-                    ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
+            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+            if (offsetTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_OFFSETS);
+            }
+            ExifTag lengthTag = mInterface
+                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            if (lengthTag == null) {
+                throw new IOException("No definition for crucial exif tag: "
+                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+            }
             long[] lengths = new long[stripCount];
             for (int i = 0; i < mExifData.getStripCount(); i++) {
                 lengths[i] = mExifData.getStrip(i).length;
@@ -329,6 +417,17 @@
             lengthTag.setValue(lengths);
             ifd1.setTag(offsetTag);
             ifd1.setTag(lengthTag);
+            // Get rid of tags for compressed if they exist.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+        } else if (ifd1 != null) {
+            // Get rid of offset and length tags if there is no thumbnail.
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+            ifd1.removeTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
         }
     }
 
@@ -336,20 +435,21 @@
         int offset = TIFF_HEADER_SIZE;
         IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
         offset = calculateOffsetOfIfd(ifd0, offset);
-        ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset);
+        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
 
         IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
         offset = calculateOffsetOfIfd(exifIfd, offset);
 
         IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
         if (interIfd != null) {
-            exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset);
+            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+                    .setValue(offset);
             offset = calculateOffsetOfIfd(interIfd, offset);
         }
 
         IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
         if (gpsIfd != null) {
-            ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset);
+            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
             offset = calculateOffsetOfIfd(gpsIfd, offset);
         }
 
@@ -361,17 +461,58 @@
 
         // thumbnail
         if (mExifData.hasCompressedThumbnail()) {
-            ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+                    .setValue(offset);
             offset += mExifData.getCompressedThumbnail().length;
-        } else if (mExifData.hasUncompressedStrip()){
+        } else if (mExifData.hasUncompressedStrip()) {
             int stripCount = mExifData.getStripCount();
             long[] offsets = new long[stripCount];
             for (int i = 0; i < mExifData.getStripCount(); i++) {
                 offsets[i] = offset;
                 offset += mExifData.getStrip(i).length;
             }
-            ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets);
+            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+                    offsets);
         }
         return offset;
     }
-}
\ No newline at end of file
+
+    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+            throws IOException {
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_ASCII:
+                byte buf[] = tag.getStringByte();
+                if (buf.length == tag.getComponentCount()) {
+                    buf[buf.length - 1] = 0;
+                    dataOutputStream.write(buf);
+                } else {
+                    dataOutputStream.write(buf);
+                    dataOutputStream.write(0);
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeInt((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeRational(tag.getRational(i));
+                }
+                break;
+            case ExifTag.TYPE_UNDEFINED:
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                buf = new byte[tag.getComponentCount()];
+                tag.getBytes(buf);
+                dataOutputStream.write(buf);
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    dataOutputStream.writeShort((short) tag.getValueAt(i));
+                }
+                break;
+        }
+    }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
index f1e52c5..5467d42 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
@@ -16,8 +16,8 @@
 
 package com.android.gallery3d.exif;
 
-import java.io.DataInputStream;
-import java.io.EOFException;
+import android.util.Log;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteOrder;
@@ -26,10 +26,13 @@
 import java.util.TreeMap;
 
 /**
- * This class provides a low-level EXIF parsing API. Given a JPEG format InputStream, the caller
- * can request which IFD's to read via {@link #parse(InputStream, int)} with given options.
+ * This class provides a low-level EXIF parsing API. Given a JPEG format
+ * InputStream, the caller can request which IFD's to read via
+ * {@link #parse(InputStream, int)} with given options.
  * <p>
- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the parser.
+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
+ * parser.
+ *
  * <pre>
  * void parse() {
  *     ExifParser parser = ExifParser.parse(mImageInputStream,
@@ -63,10 +66,12 @@
  * }
  * </pre>
  */
-public class ExifParser {
+class ExifParser {
+    private static final boolean LOGV = false;
+    private static final String TAG = "ExifParser";
     /**
-     * When the parser reaches a new IFD area. Call
-     *  {@link #getCurrentIfd()} to know which IFD we are in.
+     * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+     * know which IFD we are in.
      */
     public static final int EVENT_START_OF_IFD = 0;
     /**
@@ -76,8 +81,8 @@
     public static final int EVENT_NEW_TAG = 1;
     /**
      * When the parser reaches the value area of tag that is registered by
-     *  {@link #registerForTagValue(ExifTag)} previously. Call
-     *  {@link #getTag()} to get the corresponding tag.
+     * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+     * to get the corresponding tag.
      */
     public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
 
@@ -86,8 +91,9 @@
      */
     public static final int EVENT_COMPRESSED_IMAGE = 3;
     /**
-     * When the parser reaches the uncompressed image strip.
-     *  Call {@link #getStripIndex()} to get the index of the strip.
+     * When the parser reaches the uncompressed image strip. Call
+     * {@link #getStripIndex()} to get the index of the strip.
+     *
      * @see #getStripIndex()
      * @see #getStripCount()
      */
@@ -122,16 +128,20 @@
      */
     public static final int OPTION_THUMBNAIL = 1 << 5;
 
-    private static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
-    private static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+    protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+    protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
 
     // TIFF header
-    private static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
-    private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
-    private static final short TIFF_HEADER_TAIL = 0x002A;
+    protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+    protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+    protected static final short TIFF_HEADER_TAIL = 0x002A;
 
-    private static final int TAG_SIZE = 12;
-    private static final int OFFSET_SIZE = 2;
+    protected static final int TAG_SIZE = 12;
+    protected static final int OFFSET_SIZE = 2;
+
+    private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+    protected static final int DEFAULT_IFD0_OFFSET = 8;
 
     private final CountedDataInputStream mTiffStream;
     private final int mOptions;
@@ -145,6 +155,26 @@
     private ExifTag mJpegSizeTag;
     private boolean mNeedToParseOffsetsInCurrentIfd;
     private boolean mContainExifData = false;
+    private int mApp1End;
+    private int mOffsetToApp1EndFromSOF = 0;
+    private byte[] mDataAboveIfd0;
+    private int mIfd0Position;
+    private int mTiffStartPosition;
+    private final ExifInterface mInterface;
+
+    private static final short TAG_EXIF_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+    private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+    private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+    private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+    private static final short TAG_STRIP_OFFSETS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+    private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+            .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
 
     private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
 
@@ -168,41 +198,62 @@
         return (mOptions & OPTION_THUMBNAIL) != 0;
     }
 
-    private ExifParser(InputStream inputStream, int options)
+    private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
             throws IOException, ExifInvalidFormatException {
+        if (inputStream == null) {
+            throw new IOException("Null argument inputStream to ExifParser");
+        }
+        if (LOGV) {
+            Log.v(TAG, "Reading exif...");
+        }
+        mInterface = iRef;
         mContainExifData = seekTiffData(inputStream);
         mTiffStream = new CountedDataInputStream(inputStream);
         mOptions = options;
-        if (!mContainExifData) return;
-        if (mTiffStream.getReadByteCount() == 0) {
-            parseTiffHeader();
-            long offset = mTiffStream.readUnsignedInt();
+        if (!mContainExifData) {
+            return;
+        }
+
+        parseTiffHeader();
+        long offset = mTiffStream.readUnsignedInt();
+        if (offset > Integer.MAX_VALUE) {
+            throw new ExifInvalidFormatException("Invalid offset " + offset);
+        }
+        mIfd0Position = (int) offset;
+        mIfdType = IfdId.TYPE_IFD_0;
+        if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
             registerIfd(IfdId.TYPE_IFD_0, offset);
+            if (offset != DEFAULT_IFD0_OFFSET) {
+                mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+                read(mDataAboveIfd0);
+            }
         }
     }
 
     /**
      * Parses the the given InputStream with the given options
+     *
      * @exception IOException
      * @exception ExifInvalidFormatException
      */
-    public static ExifParser parse(InputStream inputStream, int options)
-             throws IOException, ExifInvalidFormatException {
-         return new ExifParser(inputStream, options);
+    protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
+            throws IOException, ExifInvalidFormatException {
+        return new ExifParser(inputStream, options, iRef);
     }
 
     /**
-     * Parses the the given InputStream with default options; that is, every IFD and thumbnaill
-     * will be parsed.
+     * Parses the the given InputStream with default options; that is, every IFD
+     * and thumbnaill will be parsed.
+     *
      * @exception IOException
      * @exception ExifInvalidFormatException
      * @see #parse(InputStream, int)
      */
-    public static ExifParser parse(InputStream inputStream)
+    protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
             throws IOException, ExifInvalidFormatException {
         return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
                 | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
-                | OPTION_THUMBNAIL);
+                | OPTION_THUMBNAIL, iRef);
     }
 
     /**
@@ -217,7 +268,7 @@
      * @see #EVENT_UNCOMPRESSED_STRIP
      * @see #EVENT_END
      */
-    public int next() throws IOException, ExifInvalidFormatException {
+    protected int next() throws IOException, ExifInvalidFormatException {
         if (!mContainExifData) {
             return EVENT_END;
         }
@@ -225,33 +276,59 @@
         int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
         if (offset < endOfTags) {
             mTag = readTag();
+            if (mTag == null) {
+                return next();
+            }
             if (mNeedToParseOffsetsInCurrentIfd) {
                 checkOffsetOrImageTag(mTag);
             }
             return EVENT_NEW_TAG;
         } else if (offset == endOfTags) {
-            long ifdOffset = readUnsignedLong();
             // There is a link to ifd1 at the end of ifd0
             if (mIfdType == IfdId.TYPE_IFD_0) {
+                long ifdOffset = readUnsignedLong();
                 if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
                     if (ifdOffset != 0) {
                         registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
                     }
                 }
             } else {
-                if (ifdOffset != 0) {
-                    throw new ExifInvalidFormatException("Invalid link to next IFD");
+                int offsetSize = 4;
+                // Some camera models use invalid length of the offset
+                if (mCorrespondingEvent.size() > 0) {
+                    offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+                            mTiffStream.getReadByteCount();
+                }
+                if (offsetSize < 4) {
+                    Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+                } else {
+                    long ifdOffset = readUnsignedLong();
+                    if (ifdOffset != 0) {
+                        Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+                    }
                 }
             }
         }
-        while(mCorrespondingEvent.size() != 0) {
+        while (mCorrespondingEvent.size() != 0) {
             Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
             Object event = entry.getValue();
-            skipTo(entry.getKey());
+            try {
+                skipTo(entry.getKey());
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+                        " for " + event.getClass().getName() + ", the file may be broken.");
+                continue;
+            }
             if (event instanceof IfdEvent) {
                 mIfdType = ((IfdEvent) event).ifd;
                 mNumOfTagInIfd = mTiffStream.readUnsignedShort();
                 mIfdStartOffset = entry.getKey();
+
+                if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+                    Log.w(TAG, "Invalid size of IFD " + mIfdType);
+                    return EVENT_END;
+                }
+
                 mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
                 if (((IfdEvent) event).isRequested) {
                     return EVENT_START_OF_IFD;
@@ -277,21 +354,26 @@
     }
 
     /**
-     * Skips the tags area of current IFD, if the parser is not in the tag area, nothing will
-     * happen.
+     * Skips the tags area of current IFD, if the parser is not in the tag area,
+     * nothing will happen.
      *
      * @throws IOException
      * @throws ExifInvalidFormatException
      */
-    public void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+    protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
         int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
         int offset = mTiffStream.getReadByteCount();
-        if (offset > endOfTags) return;
+        if (offset > endOfTags) {
+            return;
+        }
         if (mNeedToParseOffsetsInCurrentIfd) {
             while (offset < endOfTags) {
                 mTag = readTag();
-                checkOffsetOrImageTag(mTag);
                 offset += TAG_SIZE;
+                if (mTag == null) {
+                    continue;
+                }
+                checkOffsetOrImageTag(mTag);
             }
         } else {
             skipTo(endOfTags);
@@ -310,7 +392,8 @@
         switch (mIfdType) {
             case IfdId.TYPE_IFD_0:
                 return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS)
-                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
+                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+                        || isIfdRequested(IfdId.TYPE_IFD_1);
             case IfdId.TYPE_IFD_1:
                 return isThumbnailRequested();
             case IfdId.TYPE_IFD_EXIF:
@@ -322,38 +405,37 @@
     }
 
     /**
-     * If {@link #next()} return {@link #EVENT_NEW_TAG} or {@link #EVENT_VALUE_OF_REGISTERED_TAG},
-     * call this function to get the corresponding tag.
+     * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+     * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+     * corresponding tag.
      * <p>
-     *
-     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size of the value is
-     * greater than 4 bytes. One should call {@link ExifTag#hasValue()} to check if the tag
-     * contains value.
-     * If there is no value,call {@link #registerForTagValue(ExifTag)} to have the parser emit
-     * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area pointed by the offset.
-     *
+     * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+     * of the value is greater than 4 bytes. One should call
+     * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+     * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+     * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * pointed by the offset.
      * <p>
-     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the tag will have
-     * already been read except for tags of undefined type. For tags of undefined type, call
-     * one of the read methods to get the value.
+     * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+     * tag will have already been read except for tags of undefined type. For
+     * tags of undefined type, call one of the read methods to get the value.
      *
      * @see #registerForTagValue(ExifTag)
      * @see #read(byte[])
      * @see #read(byte[], int, int)
      * @see #readLong()
      * @see #readRational()
-     * @see #readShort()
      * @see #readString(int)
      * @see #readString(int, Charset)
      */
-    public ExifTag getTag() {
+    protected ExifTag getTag() {
         return mTag;
     }
 
     /**
      * Gets number of tags in the current IFD area.
      */
-    public int getTagCountInCurrentIfd() {
+    protected int getTagCountInCurrentIfd() {
         return mNumOfTagInIfd;
     }
 
@@ -366,51 +448,49 @@
      * @see IfdId#TYPE_IFD_INTEROPERABILITY
      * @see IfdId#TYPE_IFD_EXIF
      */
-    public int getCurrentIfd() {
+    protected int getCurrentIfd() {
         return mIfdType;
     }
 
     /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP},
-     * call this function to get the index of this strip.
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the index of this strip.
+     *
      * @see #getStripCount()
      */
-    public int getStripIndex() {
+    protected int getStripIndex() {
         return mImageEvent.stripIndex;
     }
 
     /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number
-     * of strip data.
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the number of strip data.
+     *
      * @see #getStripIndex()
      */
-    public int getStripCount() {
+    protected int getStripCount() {
         return mStripCount;
     }
 
     /**
-     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size.
+     * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+     * get the strip size.
      */
-    public int getStripSize() {
-        if (mStripSizeTag == null) return 0;
-        if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-            return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex);
-        } else {
-            // Cast unsigned int to int since the strip size is always smaller
-            // than the size of APP1 (65536)
-            return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex);
-        }
+    protected int getStripSize() {
+        if (mStripSizeTag == null)
+            return 0;
+        return (int) mStripSizeTag.getValueAt(0);
     }
 
     /**
-     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data
-     * size.
+     * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+     * the image data size.
      */
-    public int getCompressedImageSize() {
-        if (mJpegSizeTag == null) return 0;
-        // Cast unsigned int to int since the thumbnail is always smaller
-        // than the size of APP1 (65536)
-        return (int) mJpegSizeTag.getUnsignedLong(0);
+    protected int getCompressedImageSize() {
+        if (mJpegSizeTag == null) {
+            return 0;
+        }
+        return (int) mJpegSizeTag.getValueAt(0);
     }
 
     private void skipTo(int offset) throws IOException {
@@ -421,15 +501,18 @@
     }
 
     /**
-     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD,
-     * the tag may not contain the value if the size of the value is greater than 4 bytes.
-     * When the value is not available here, call this method so that the parser will emit
-     * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located.
-
+     * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+     * not contain the value if the size of the value is greater than 4 bytes.
+     * When the value is not available here, call this method so that the parser
+     * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+     * where the value is located.
+     *
      * @see #EVENT_VALUE_OF_REGISTERED_TAG
      */
-    public void registerForTagValue(ExifTag tag) {
-        mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+    protected void registerForTagValue(ExifTag tag) {
+        if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
+            mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
+        }
     }
 
     private void registerIfd(int ifdType, long offset) {
@@ -455,7 +538,15 @@
             throw new ExifInvalidFormatException(
                     "Number of component is larger then Integer.MAX_VALUE");
         }
-        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
+        // Some invalid image file contains invalid data type. Ignore those tags
+        if (!ExifTag.isValidType(dataFormat)) {
+            Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+            mTiffStream.skip(4);
+            return null;
+        }
+        // TODO: handle numOfComp overflow
+        ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+                ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
         int dataSize = tag.getDataSize();
         if (dataSize > 4) {
             long offset = mTiffStream.readUnsignedInt();
@@ -463,136 +554,188 @@
                 throw new ExifInvalidFormatException(
                         "offset is larger then Integer.MAX_VALUE");
             }
-            tag.setOffset((int) offset);
+            // Some invalid images put some undefined data before IFD0.
+            // Read the data here.
+            if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+                byte[] buf = new byte[(int) numOfComp];
+                System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+                        buf, 0, (int) numOfComp);
+                tag.setValue(buf);
+            } else {
+                tag.setOffset((int) offset);
+            }
         } else {
+            boolean defCount = tag.hasDefinedCount();
+            // Set defined count to 0 so we can add \0 to non-terminated strings
+            tag.setHasDefinedCount(false);
+            // Read value
             readFullTagValue(tag);
+            tag.setHasDefinedCount(defCount);
             mTiffStream.skip(4 - dataSize);
+            // Set the offset to the position of value.
+            tag.setOffset(mTiffStream.getReadByteCount() - 4);
         }
         return tag;
     }
 
     /**
-     * Check the tag, if the tag is one of the offset tag that points to the IFD or image the
-     * caller is interested in, register the IFD or image.
+     * Check the tag, if the tag is one of the offset tag that points to the IFD
+     * or image the caller is interested in, register the IFD or image.
      */
     private void checkOffsetOrImageTag(ExifTag tag) {
-        switch (tag.getTagId()) {
-            case ExifTag.TAG_EXIF_IFD:
-                if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
-                        || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                    registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedLong(0));
-                }
-                break;
-            case ExifTag.TAG_GPS_IFD:
-                if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
-                    registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedLong(0));
-                }
-                break;
-            case ExifTag.TAG_INTEROPERABILITY_IFD:
-                if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
-                    registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedLong(0));
-                }
-                break;
-            case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT:
-                if (isThumbnailRequested()) {
-                    registerCompressedImage(tag.getUnsignedLong(0));
-                }
-                break;
-            case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
-                if (isThumbnailRequested()) {
-                    mJpegSizeTag = tag;
-                }
-                break;
-            case ExifTag.TAG_STRIP_OFFSETS:
-                if (isThumbnailRequested()) {
-                    if (tag.hasValue()) {
-                        for (int i = 0; i < tag.getComponentCount(); i++) {
-                            if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-                                registerUncompressedStrip(i, tag.getUnsignedShort(i));
-                            } else {
-                                registerUncompressedStrip(i, tag.getUnsignedLong(i));
-                            }
+        // Some invalid formattd image contains tag with 0 size.
+        if (tag.getComponentCount() == 0) {
+            return;
+        }
+        short tid = tag.getTagId();
+        int ifd = tag.getIfd();
+        if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+                    || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+                registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_INTEROPERABILITY_IFD
+                && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+            if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+                registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+            if (isThumbnailRequested()) {
+                registerCompressedImage(tag.getValueAt(0));
+            }
+        } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+                && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+            if (isThumbnailRequested()) {
+                mJpegSizeTag = tag;
+            }
+        } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+            if (isThumbnailRequested()) {
+                if (tag.hasValue()) {
+                    for (int i = 0; i < tag.getComponentCount(); i++) {
+                        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
+                        } else {
+                            registerUncompressedStrip(i, tag.getValueAt(i));
                         }
-                    } else {
-                        mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
                     }
+                } else {
+                    mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
                 }
-                break;
-            case ExifTag.TAG_STRIP_BYTE_COUNTS:
-                if (isThumbnailRequested()) {
-                    if (tag.hasValue()) {
-                        mStripSizeTag = tag;
-                    }
-                }
-                break;
+            }
+        } else if (tid == TAG_STRIP_BYTE_COUNTS
+                && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+                &&isThumbnailRequested() && tag.hasValue()) {
+            mStripSizeTag = tag;
         }
     }
 
-    private void readFullTagValue(ExifTag tag) throws IOException {
-        switch(tag.getDataType()) {
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-            case ExifTag.TYPE_UNDEFINED:
-                {
-                    byte buf[] = new byte[tag.getComponentCount()];
-                    read(buf);
-                    tag.setValue(buf);
+    private boolean checkAllowed(int ifd, int tagId) {
+        int info = mInterface.getTagInfo().get(tagId);
+        if (info == ExifInterface.DEFINITION_NULL) {
+            return false;
+        }
+        return ExifInterface.isIfdAllowed(info, ifd);
+    }
+
+    protected void readFullTagValue(ExifTag tag) throws IOException {
+        // Some invalid images contains tags with wrong size, check it here
+        short type = tag.getDataType();
+        if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+                type == ExifTag.TYPE_UNSIGNED_BYTE) {
+            int size = tag.getComponentCount();
+            if (mCorrespondingEvent.size() > 0) {
+                if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+                        + size) {
+                    Object event = mCorrespondingEvent.firstEntry().getValue();
+                    if (event instanceof ImageEvent) {
+                        // Tag value overlaps thumbnail, ignore thumbnail.
+                        Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
+                        Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+                        Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+                    } else {
+                        // Tag value overlaps another tag, shorten count
+                        if (event instanceof IfdEvent) {
+                            Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        } else if (event instanceof ExifTagEvent) {
+                            Log.w(TAG, "Tag value for tag: \n"
+                                    + ((ExifTagEvent) event).tag.toString()
+                                    + " overlaps value for tag: \n" + tag.toString());
+                        }
+                        size = mCorrespondingEvent.firstEntry().getKey()
+                                - mTiffStream.getReadByteCount();
+                        Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+                                + " setting count to: " + size);
+                        tag.forceSetComponentCount(size);
+                    }
                 }
+            }
+        }
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+            case ExifTag.TYPE_UNDEFINED: {
+                byte buf[] = new byte[tag.getComponentCount()];
+                read(buf);
+                tag.setValue(buf);
+            }
                 break;
             case ExifTag.TYPE_ASCII:
                 tag.setValue(readString(tag.getComponentCount()));
                 break;
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                {
-                    long value[] = new long[tag.getComponentCount()];
-                    for (int i = 0, n = value.length; i < n; i++) {
-                        value[i] = readUnsignedLong();
-                    }
-                    tag.setValue(value);
+            case ExifTag.TYPE_UNSIGNED_LONG: {
+                long value[] = new long[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedLong();
                 }
+                tag.setValue(value);
+            }
                 break;
-          case ExifTag.TYPE_UNSIGNED_RATIONAL:
-              {
-                  Rational value[] = new Rational[tag.getComponentCount()];
-                  for (int i = 0, n = value.length; i < n; i++) {
-                      value[i] = readUnsignedRational();
-                  }
-                  tag.setValue(value);
-              }
-              break;
-          case ExifTag.TYPE_UNSIGNED_SHORT:
-              {
-                  int value[] = new int[tag.getComponentCount()];
-                  for (int i = 0, n = value.length; i < n; i++) {
-                      value[i] = readUnsignedShort();
-                  }
-                  tag.setValue(value);
-              }
-              break;
-          case ExifTag.TYPE_LONG:
-              {
-                  int value[] = new int[tag.getComponentCount()];
-                  for (int i = 0, n = value.length; i < n; i++) {
-                      value[i] = readLong();
-                  }
-                  tag.setValue(value);
-              }
-              break;
-          case ExifTag.TYPE_RATIONAL:
-              {
-                  Rational value[] = new Rational[tag.getComponentCount()];
-                  for (int i = 0, n = value.length; i < n; i++) {
-                      value[i] = readRational();
-                  }
-                  tag.setValue(value);
-              }
-              break;
+            case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readUnsignedShort();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_LONG: {
+                int value[] = new int[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readLong();
+                }
+                tag.setValue(value);
+            }
+                break;
+            case ExifTag.TYPE_RATIONAL: {
+                Rational value[] = new Rational[tag.getComponentCount()];
+                for (int i = 0, n = value.length; i < n; i++) {
+                    value[i] = readRational();
+                }
+                tag.setValue(value);
+            }
+                break;
+        }
+        if (LOGV) {
+            Log.v(TAG, "\n" + tag.toString());
         }
     }
 
     private void parseTiffHeader() throws IOException,
             ExifInvalidFormatException {
         short byteOrder = mTiffStream.readShort();
-        ByteOrder order;
         if (LITTLE_ENDIAN_TAG == byteOrder) {
             mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
         } else if (BIG_ENDIAN_TAG == byteOrder) {
@@ -608,89 +751,106 @@
 
     private boolean seekTiffData(InputStream inputStream) throws IOException,
             ExifInvalidFormatException {
-        DataInputStream dataStream = new DataInputStream(inputStream);
-
-        // SOI and APP1
+        CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
         if (dataStream.readShort() != JpegHeader.SOI) {
             throw new ExifInvalidFormatException("Invalid JPEG format");
         }
 
         short marker = dataStream.readShort();
-        while(marker != JpegHeader.APP1 && marker != JpegHeader.EOI
+        while (marker != JpegHeader.EOI
                 && !JpegHeader.isSofMarker(marker)) {
             int length = dataStream.readUnsignedShort();
-            if ((length - 2) != dataStream.skip(length - 2)) {
-                throw new EOFException();
+            // Some invalid formatted image contains multiple APP1,
+            // try to find the one with Exif data.
+            if (marker == JpegHeader.APP1) {
+                int header = 0;
+                short headerTail = 0;
+                if (length >= 8) {
+                    header = dataStream.readInt();
+                    headerTail = dataStream.readShort();
+                    length -= 6;
+                    if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+                        mTiffStartPosition = dataStream.getReadByteCount();
+                        mApp1End = length;
+                        mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
+                        return true;
+                    }
+                }
+            }
+            if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+                Log.w(TAG, "Invalid JPEG format.");
+                return false;
             }
             marker = dataStream.readShort();
         }
+        return false;
+    }
 
-        if (marker != JpegHeader.APP1) return false; // No APP1 segment
+    protected int getOffsetToExifEndFromSOF() {
+        return mOffsetToApp1EndFromSOF;
+    }
 
-        // APP1 length, it's not used for us
-        dataStream.readShort();
-
-        // Exif header
-        return (dataStream.readInt() == EXIF_HEADER
-                && dataStream.readShort() == EXIF_HEADER_TAIL);
+    protected int getTiffStartPosition() {
+        return mTiffStartPosition;
     }
 
     /**
      * Reads bytes from the InputStream.
      */
-    public int read(byte[] buffer, int offset, int length) throws IOException {
+    protected int read(byte[] buffer, int offset, int length) throws IOException {
         return mTiffStream.read(buffer, offset, length);
     }
 
     /**
      * Equivalent to read(buffer, 0, buffer.length).
      */
-    public int read(byte[] buffer) throws IOException {
+    protected int read(byte[] buffer) throws IOException {
         return mTiffStream.read(buffer);
     }
 
     /**
-     * Reads a String from the InputStream with UTF8 charset.
-     * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+     * Reads a String from the InputStream with US-ASCII charset. The parser
+     * will read n bytes and convert it to ascii string. This is used for
+     * reading values of type {@link ExifTag#TYPE_ASCII}.
      */
-    public String readString(int n) throws IOException {
+    protected String readString(int n) throws IOException {
+        return readString(n, US_ASCII);
+    }
+
+    /**
+     * Reads a String from the InputStream with the given charset. The parser
+     * will read n bytes and convert it to string. This is used for reading
+     * values of type {@link ExifTag#TYPE_ASCII}.
+     */
+    protected String readString(int n, Charset charset) throws IOException {
         if (n > 0) {
-            byte[] buf = new byte[n];
-            mTiffStream.readOrThrow(buf);
-            return new String(buf, 0, n - 1, "UTF8");
+            return mTiffStream.readString(n, charset);
         } else {
             return "";
         }
     }
 
     /**
-     * Reads a String from the InputStream with the given charset.
-     * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+     * InputStream.
      */
-    public String readString(int n, Charset charset) throws IOException {
-        byte[] buf = new byte[n];
-        mTiffStream.readOrThrow(buf);
-        return new String(buf, 0, n - 1, charset);
-    }
-
-    /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the InputStream.
-     */
-    public int readUnsignedShort() throws IOException {
+    protected int readUnsignedShort() throws IOException {
         return mTiffStream.readShort() & 0xffff;
     }
 
     /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the InputStream.
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+     * InputStream.
      */
-    public long readUnsignedLong() throws IOException {
+    protected long readUnsignedLong() throws IOException {
         return readLong() & 0xffffffffL;
     }
 
     /**
-     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the InputStream.
+     * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+     * InputStream.
      */
-    public Rational readUnsignedRational() throws IOException {
+    protected Rational readUnsignedRational() throws IOException {
         long nomi = readUnsignedLong();
         long denomi = readUnsignedLong();
         return new Rational(nomi, denomi);
@@ -699,14 +859,14 @@
     /**
      * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
      */
-    public int readLong() throws IOException {
+    protected int readLong() throws IOException {
         return mTiffStream.readInt();
     }
 
     /**
      * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
      */
-    public Rational readRational() throws IOException {
+    protected Rational readRational() throws IOException {
         int nomi = readLong();
         int denomi = readLong();
         return new Rational(nomi, denomi);
@@ -715,10 +875,12 @@
     private static class ImageEvent {
         int stripIndex;
         int type;
+
         ImageEvent(int type) {
             this.stripIndex = 0;
             this.type = type;
         }
+
         ImageEvent(int type, int stripIndex) {
             this.type = type;
             this.stripIndex = stripIndex;
@@ -728,6 +890,7 @@
     private static class IfdEvent {
         int ifd;
         boolean isRequested;
+
         IfdEvent(int ifd, boolean isInterestedIfd) {
             this.ifd = ifd;
             this.isRequested = isInterestedIfd;
@@ -737,6 +900,7 @@
     private static class ExifTagEvent {
         ExifTag tag;
         boolean isRequested;
+
         ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
             this.tag = tag;
             this.isRequested = isRequireByUser;
@@ -746,7 +910,7 @@
     /**
      * Gets the byte order of the current InputStream.
      */
-    public ByteOrder getByteOrder() {
+    protected ByteOrder getByteOrder() {
         return mTiffStream.getByteOrder();
     }
-}
\ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
index d8083b2..68e972f 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
@@ -16,22 +16,36 @@
 
 package com.android.gallery3d.exif;
 
+import android.util.Log;
+
 import java.io.IOException;
 import java.io.InputStream;
 
 /**
- * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}.
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
  */
-public class ExifReader {
+class ExifReader {
+    private static final String TAG = "ExifReader";
+
+    private final ExifInterface mInterface;
+
+    ExifReader(ExifInterface iRef) {
+        mInterface = iRef;
+    }
+
     /**
-     * Parses the inputStream and  and returns the EXIF data in an {@link ExifData}.
+     * Parses the inputStream and and returns the EXIF data in an
+     * {@link ExifData}.
+     *
      * @throws ExifInvalidFormatException
      * @throws IOException
      */
-    public ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+    protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
             IOException {
-        ExifParser parser = ExifParser.parse(inputStream);
+        ExifParser parser = ExifParser.parse(inputStream, mInterface);
         ExifData exifData = new ExifData(parser.getByteOrder());
+        ExifTag tag = null;
 
         int event = parser.next();
         while (event != ExifParser.EVENT_END) {
@@ -40,7 +54,7 @@
                     exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
                     break;
                 case ExifParser.EVENT_NEW_TAG:
-                    ExifTag tag = parser.getTag();
+                    tag = parser.getTag();
                     if (!tag.hasValue()) {
                         parser.registerForTagValue(tag);
                     } else {
@@ -50,25 +64,29 @@
                 case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
                     tag = parser.getTag();
                     if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
-                        byte[] buf = new byte[tag.getComponentCount()];
-                        parser.read(buf);
-                        tag.setValue(buf);
+                        parser.readFullTagValue(tag);
                     }
                     exifData.getIfdData(tag.getIfd()).setTag(tag);
                     break;
                 case ExifParser.EVENT_COMPRESSED_IMAGE:
                     byte buf[] = new byte[parser.getCompressedImageSize()];
-                    parser.read(buf);
-                    exifData.setCompressedThumbnail(buf);
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setCompressedThumbnail(buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the compressed thumbnail");
+                    }
                     break;
                 case ExifParser.EVENT_UNCOMPRESSED_STRIP:
                     buf = new byte[parser.getStripSize()];
-                    parser.read(buf);
-                    exifData.setStripBytes(parser.getStripIndex(), buf);
+                    if (buf.length == parser.read(buf)) {
+                        exifData.setStripBytes(parser.getStripIndex(), buf);
+                    } else {
+                        Log.w(TAG, "Failed to read the strip bytes");
+                    }
                     break;
             }
             event = parser.next();
         }
         return exifData;
     }
-}
\ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
index 49cb6ed..b8b3872 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
@@ -16,487 +16,26 @@
 
 package com.android.gallery3d.exif;
 
-import android.util.SparseArray;
-
+import java.nio.charset.Charset;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Date;
 
 /**
- * This class stores information of an EXIF tag.
- * @see ExifParser
- * @see ExifReader
- * @see IfdData
- * @see ExifData
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
  */
 public class ExifTag {
-    // Tiff Tags
-    public static final short TAG_IMAGE_WIDTH = 0x100;
-    /*
-     * The height of the image.
-     */
-    public static final short TAG_IMAGE_LENGTH = 0x101;
-    public static final short TAG_BITS_PER_SAMPLE = 0x102;
-    public static final short TAG_COMPRESSION = 0x103;
-    public static final short TAG_PHOTOMETRIC_INTERPRETATION = 0x106;
-    public static final short TAG_IMAGE_DESCRIPTION = 0x10E;
-    public static final short TAG_MAKE = 0x10F;
-    public static final short TAG_MODEL = 0x110;
-    public static final short TAG_STRIP_OFFSETS = 0x111;
-    public static final short TAG_ORIENTATION = 0x112;
-    public static final short TAG_SAMPLES_PER_PIXEL = 0x115;
-    public static final short TAG_ROWS_PER_STRIP = 0x116;
-    public static final short TAG_STRIP_BYTE_COUNTS = 0x117;
-    public static final short TAG_X_RESOLUTION = 0x11A;
-    public static final short TAG_Y_RESOLUTION = 0x11B;
-    public static final short TAG_PLANAR_CONFIGURATION = 0x11C;
-    public static final short TAG_RESOLUTION_UNIT = 0x128;
-    public static final short TAG_TRANSFER_FUNCTION = 0x12D;
-    public static final short TAG_SOFTWARE = 0x131;
-    public static final short TAG_DATE_TIME = 0x132;
-    public static final short TAG_ARTIST = 0x13B;
-    public static final short TAG_WHITE_POINT = 0x13E;
-    public static final short TAG_PRIMARY_CHROMATICITIES = 0x13F;
-    public static final short TAG_JPEG_INTERCHANGE_FORMAT = 0x201;
-    public static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x202;
-    public static final short TAG_Y_CB_CR_COEFFICIENTS = 0x211;
-    public static final short TAG_Y_CB_CR_SUB_SAMPLING = 0x212;
-    public static final short TAG_Y_CB_CR_POSITIONING = 0x213;
-    public static final short TAG_REFERENCE_BLACK_WHITE = 0x214;
-    public static final short TAG_COPYRIGHT = (short) 0x8298;
-    public static final short TAG_EXIF_IFD = (short) 0x8769;
-    public static final short TAG_GPS_IFD = (short) 0x8825;
-
-    // Exif Tags
-    public static final short TAG_EXPOSURE_TIME = (short) 0x829A;
-    public static final short TAG_F_NUMBER = (short) 0x829D;
-    public static final short TAG_EXPOSURE_PROGRAM = (short) 0x8822;
-    public static final short TAG_SPECTRAL_SENSITIVITY = (short) 0x8824;
-    public static final short TAG_ISO_SPEED_RATINGS = (short) 0x8827;
-    public static final short TAG_OECF = (short) 0x8828;
-    public static final short TAG_EXIF_VERSION = (short) 0x9000;
-    public static final short TAG_DATE_TIME_ORIGINAL = (short) 0x9003;
-    public static final short TAG_DATE_TIME_DIGITIZED = (short) 0x9004;
-    public static final short TAG_COMPONENTS_CONFIGURATION = (short) 0x9101;
-    public static final short TAG_COMPRESSED_BITS_PER_PIXEL = (short) 0x9102;
-    public static final short TAG_SHUTTER_SPEED_VALUE = (short) 0x9201;
-    public static final short TAG_APERTURE_VALUE = (short) 0x9202;
-    public static final short TAG_BRIGHTNESS_VALUE = (short) 0x9203;
-    public static final short TAG_EXPOSURE_BIAS_VALUE = (short) 0x9204;
-    public static final short TAG_MAX_APERTURE_VALUE = (short) 0x9205;
-    public static final short TAG_SUBJECT_DISTANCE = (short) 0x9206;
-    public static final short TAG_METERING_MODE = (short) 0x9207;
-    public static final short TAG_LIGHT_SOURCE = (short) 0x9208;
-    public static final short TAG_FLASH = (short) 0x9209;
-    public static final short TAG_FOCAL_LENGTH = (short) 0x920A;
-    public static final short TAG_SUBJECT_AREA = (short) 0x9214;
-    public static final short TAG_MAKER_NOTE = (short) 0x927C;
-    public static final short TAG_USER_COMMENT = (short) 0x9286;
-    public static final short TAG_SUB_SEC_TIME = (short) 0x9290;
-    public static final short TAG_SUB_SEC_TIME_ORIGINAL = (short) 0x9291;
-    public static final short TAG_SUB_SEC_TIME_DIGITIZED = (short) 0x9292;
-    public static final short TAG_FLASHPIX_VERSION = (short) 0xA000;
-    public static final short TAG_COLOR_SPACE = (short) 0xA001;
-    public static final short TAG_PIXEL_X_DIMENSION = (short) 0xA002;
-    public static final short TAG_PIXEL_Y_DIMENSION = (short) 0xA003;
-    public static final short TAG_RELATED_SOUND_FILE = (short) 0xA004;
-    public static final short TAG_INTEROPERABILITY_IFD = (short) 0xA005;
-    public static final short TAG_FLASH_ENERGY = (short) 0xA20B;
-    public static final short TAG_SPATIAL_FREQUENCY_RESPONSE = (short) 0xA20C;
-    public static final short TAG_FOCAL_PLANE_X_RESOLUTION = (short) 0xA20E;
-    public static final short TAG_FOCAL_PLANE_Y_RESOLUTION = (short) 0xA20F;
-    public static final short TAG_FOCAL_PLANE_RESOLUTION_UNIT = (short) 0xA210;
-    public static final short TAG_SUBJECT_LOCATION = (short) 0xA214;
-    public static final short TAG_EXPOSURE_INDEX = (short) 0xA215;
-    public static final short TAG_SENSING_METHOD = (short) 0xA217;
-    public static final short TAG_FILE_SOURCE = (short) 0xA300;
-    public static final short TAG_SCENE_TYPE = (short) 0xA301;
-    public static final short TAG_CFA_PATTERN = (short) 0xA302;
-    public static final short TAG_CUSTOM_RENDERED = (short) 0xA401;
-    public static final short TAG_EXPOSURE_MODE = (short) 0xA402;
-    public static final short TAG_WHITE_BALANCE = (short) 0xA403;
-    public static final short TAG_DIGITAL_ZOOM_RATIO = (short) 0xA404;
-    public static final short TAG_FOCAL_LENGTH_IN_35_MM_FILE = (short) 0xA405;
-    public static final short TAG_SCENE_CAPTURE_TYPE = (short) 0xA406;
-    public static final short TAG_GAIN_CONTROL = (short) 0xA407;
-    public static final short TAG_CONTRAST = (short) 0xA408;
-    public static final short TAG_SATURATION = (short) 0xA409;
-    public static final short TAG_SHARPNESS = (short) 0xA40A;
-    public static final short TAG_DEVICE_SETTING_DESCRIPTION = (short) 0xA40B;
-    public static final short TAG_SUBJECT_DISTANCE_RANGE = (short) 0xA40C;
-    public static final short TAG_IMAGE_UNIQUE_ID = (short) 0xA420;
-
-    // GPS tags
-    public static final short TAG_GPS_VERSION_ID = 0;
-    public static final short TAG_GPS_LATITUDE_REF = 1;
-    public static final short TAG_GPS_LATITUDE = 2;
-    public static final short TAG_GPS_LONGITUDE_REF = 3;
-    public static final short TAG_GPS_LONGITUDE = 4;
-    public static final short TAG_GPS_ALTITUDE_REF = 5;
-    public static final short TAG_GPS_ALTITUDE = 6;
-    public static final short TAG_GPS_TIME_STAMP = 7;
-    public static final short TAG_GPS_SATTELLITES = 8;
-    public static final short TAG_GPS_STATUS = 9;
-    public static final short TAG_GPS_MEASURE_MODE = 10;
-    public static final short TAG_GPS_DOP = 11;
-    public static final short TAG_GPS_SPEED_REF = 12;
-    public static final short TAG_GPS_SPEED = 13;
-    public static final short TAG_GPS_TRACK_REF = 14;
-    public static final short TAG_GPS_TRACK = 15;
-    public static final short TAG_GPS_IMG_DIRECTION_REF = 16;
-    public static final short TAG_GPS_IMG_DIRECTION = 17;
-    public static final short TAG_GPS_MAP_DATUM = 18;
-    public static final short TAG_GPS_DEST_LATITUDE_REF = 19;
-    public static final short TAG_GPS_DEST_LATITUDE = 20;
-    public static final short TAG_GPS_DEST_LONGITUDE_REF = 21;
-    public static final short TAG_GPS_DEST_LONGITUDE = 22;
-    public static final short TAG_GPS_DEST_BEARING_REF = 23;
-    public static final short TAG_GPS_DEST_BEARING = 24;
-    public static final short TAG_GPS_DEST_DISTANCE_REF = 25;
-    public static final short TAG_GPS_DEST_DISTANCE = 26;
-    public static final short TAG_GPS_PROCESSING_METHOD = 27;
-    public static final short TAG_GPS_AREA_INFORMATION = 28;
-    public static final short TAG_GPS_DATA_STAMP = 29;
-    public static final short TAG_GPS_DIFFERENTIAL = 30;
-
-    // Interoperability tag
-    public static final short TAG_INTEROPERABILITY_INDEX = 1;
-
-    /**
-     * Constants for {@link #TAG_ORIENTATION}
-     */
-    public static interface Orientation {
-        public static final short TOP_LEFT = 1;
-        public static final short TOP_RIGHT = 2;
-        public static final short BOTTOM_LEFT = 3;
-        public static final short BOTTOM_RIGHT = 4;
-        public static final short LEFT_TOP = 5;
-        public static final short RIGHT_TOP = 6;
-        public static final short LEFT_BOTTOM = 7;
-        public static final short RIGHT_BOTTOM = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
-     */
-    public static interface YCbCrPositioning {
-        public static final short CENTERED = 1;
-        public static final short CO_SITED = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_COMPRESSION}
-     */
-    public static interface Compression {
-        public static final short UNCOMPRESSION = 1;
-        public static final short JPEG = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_RESOLUTION_UNIT}
-     */
-    public static interface ResolutionUnit {
-        public static final short INCHES = 2;
-        public static final short CENTIMETERS = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
-     */
-    public static interface PhotometricInterpretation {
-        public static final short RGB = 2;
-        public static final short YCBCR = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_PLANAR_CONFIGURATION}
-     */
-    public static interface PlanarConfiguration {
-        public static final short CHUNKY = 1;
-        public static final short PLANAR = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_EXPOSURE_PROGRAM}
-     */
-    public static interface ExposureProgram {
-        public static final short NOT_DEFINED = 0;
-        public static final short MANUAL = 1;
-        public static final short NORMAL_PROGRAM = 2;
-        public static final short APERTURE_PRIORITY = 3;
-        public static final short SHUTTER_PRIORITY = 4;
-        public static final short CREATIVE_PROGRAM = 5;
-        public static final short ACTION_PROGRAM = 6;
-        public static final short PROTRAIT_MODE = 7;
-        public static final short LANDSCAPE_MODE = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_METERING_MODE}
-     */
-    public static interface MeteringMode {
-        public static final short UNKNOWN = 0;
-        public static final short AVERAGE = 1;
-        public static final short CENTER_WEIGHTED_AVERAGE = 2;
-        public static final short SPOT = 3;
-        public static final short MULTISPOT = 4;
-        public static final short PATTERN = 5;
-        public static final short PARTAIL = 6;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2 standard, we can
-     * treat this constant as bitwise flag.
-     * <p>
-     * e.g.
-     * <p>
-     * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | MODE_AUTO_MODE
-     */
-    public static interface Flash {
-        // LSB
-        public static final short DID_NOT_FIRED = 0;
-        public static final short FIRED = 1;
-        // 1st~2nd bits
-        public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1;
-        public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1;
-        // 3rd~4th bits
-        public static final short MODE_UNKNOWN = 0 << 3;
-        public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3;
-        public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3;
-        public static final short MODE_AUTO_MODE = 3 << 3;
-        // 5th bit
-        public static final short FUNCTION_PRESENT = 0 << 5;
-        public static final short FUNCTION_NO_FUNCTION = 1 << 5;
-        // 6th bit
-        public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
-        public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_COLOR_SPACE}
-     */
-    public static interface ColorSpace {
-        public static final short SRGB = 1;
-        public static final short UNCALIBRATED = (short) 0xFFFF;
-    }
-
-    /**
-     * Constants for {@link #TAG_EXPOSURE_MODE}
-     */
-    public static interface ExposureMode {
-        public static final short AUTO_EXPOSURE = 0;
-        public static final short MANUAL_EXPOSURE = 1;
-        public static final short AUTO_BRACKET = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_WHITE_BALANCE}
-     */
-    public static interface WhiteBalance {
-        public static final short AUTO = 0;
-        public static final short MANUAL = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
-     */
-    public static interface SceneCapture {
-        public static final short STANDARD = 0;
-        public static final short LANDSCAPE = 1;
-        public static final short PROTRAIT = 2;
-        public static final short NIGHT_SCENE = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
-     */
-    public static interface ComponentsConfiguration {
-        public static final short NOT_EXIST = 0;
-        public static final short Y = 1;
-        public static final short CB = 2;
-        public static final short CR = 3;
-        public static final short R = 4;
-        public static final short G = 5;
-        public static final short B = 6;
-    }
-
-    /**
-     * Constants for {@link #TAG_LIGHT_SOURCE}
-     */
-    public static interface LightSource {
-        public static final short UNKNOWN = 0;
-        public static final short DAYLIGHT = 1;
-        public static final short FLUORESCENT = 2;
-        public static final short TUNGSTEN = 3;
-        public static final short FLASH = 4;
-        public static final short FINE_WEATHER = 9;
-        public static final short CLOUDY_WEATHER = 10;
-        public static final short SHADE = 11;
-        public static final short DAYLIGHT_FLUORESCENT = 12;
-        public static final short DAY_WHITE_FLUORESCENT = 13;
-        public static final short COOL_WHITE_FLUORESCENT = 14;
-        public static final short WHITE_FLUORESCENT = 15;
-        public static final short STANDARD_LIGHT_A = 17;
-        public static final short STANDARD_LIGHT_B = 18;
-        public static final short STANDARD_LIGHT_C = 19;
-        public static final short D55 = 20;
-        public static final short D65 = 21;
-        public static final short D75 = 22;
-        public static final short D50 = 23;
-        public static final short ISO_STUDIO_TUNGSTEN = 24;
-        public static final short OTHER = 255;
-    }
-
-    /**
-     * Constants for {@link #TAG_SENSING_METHOD}
-     */
-    public static interface SensingMethod {
-        public static final short NOT_DEFINED = 1;
-        public static final short ONE_CHIP_COLOR = 2;
-        public static final short TWO_CHIP_COLOR = 3;
-        public static final short THREE_CHIP_COLOR = 4;
-        public static final short COLOR_SEQUENTIAL_AREA = 5;
-        public static final short TRILINEAR = 7;
-        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
-    }
-
-    /**
-     * Constants for {@link #TAG_FILE_SOURCE}
-     */
-    public static interface FileSource {
-        public static final short DSC = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_SCENE_TYPE}
-     */
-    public static interface SceneType {
-        public static final short DIRECT_PHOTOGRAPHED = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_GAIN_CONTROL}
-     */
-    public static interface GainControl {
-        public static final short NONE = 0;
-        public static final short LOW_UP = 1;
-        public static final short HIGH_UP = 2;
-        public static final short LOW_DOWN = 3;
-        public static final short HIGH_DOWN = 4;
-    }
-
-    /**
-     * Constants for {@link #TAG_CONTRAST}
-     */
-    public static interface Contrast {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SATURATION}
-     */
-    public static interface Saturation {
-        public static final short NORMAL = 0;
-        public static final short LOW = 1;
-        public static final short HIGH = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SHARPNESS}
-     */
-    public static interface Sharpness {
-        public static final short NORMAL = 0;
-        public static final short SOFT = 1;
-        public static final short HARD = 2;
-    }
-
-    /**
-     * Constants for {@link #TAG_SUBJECT_DISTANCE}
-     */
-    public static interface SubjectDistance {
-        public static final short UNKNOWN = 0;
-        public static final short MACRO = 1;
-        public static final short CLOSE_VIEW = 2;
-        public static final short DISTANT_VIEW = 3;
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_LATITUDE_REF}, {@link #TAG_GPS_DEST_LATITUDE_REF}
-     */
-    public static interface GpsLatitudeRef {
-        public static final String NORTH = "N";
-        public static final String SOUTH = "S";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_LONGITUDE_REF}, {@link #TAG_GPS_DEST_LONGITUDE_REF}
-     */
-    public static interface GpsLongitudeRef {
-        public static final String EAST = "E";
-        public static final String WEST = "W";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_ALTITUDE_REF}
-     */
-    public static interface GpsAltitudeRef {
-        public static final short SEA_LEVEL = 0;
-        public static final short SEA_LEVEL_NEGATIVE = 1;
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_STATUS}
-     */
-    public static interface GpsStatus {
-        public static final String IN_PROGRESS = "A";
-        public static final String INTEROPERABILITY = "V";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_MEASURE_MODE}
-     */
-    public static interface GpsMeasureMode {
-        public static final String MODE_2_DIMENSIONAL = "2";
-        public static final String MODE_3_DIMENSIONAL = "3";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_SPEED_REF}, {@link #TAG_GPS_DEST_DISTANCE_REF}
-     */
-    public static interface GpsSpeedRef {
-        public static final String KILOMETERS = "K";
-        public static final String MILES = "M";
-        public static final String KNOTS = "N";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_TRACK_REF}, {@link #TAG_GPS_IMG_DIRECTION_REF},
-     * {@link #TAG_GPS_DEST_BEARING_REF}
-     */
-    public static interface GpsTrackRef {
-        public static final String TRUE_DIRECTION = "T";
-        public static final String MAGNETIC_DIRECTION = "M";
-    }
-
-    /**
-     * Constants for {@link #TAG_GPS_DIFFERENTIAL}
-     */
-    public static interface GpsDifferential {
-        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
-        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
-    }
-
     /**
      * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
      */
     public static final short TYPE_UNSIGNED_BYTE = 1;
     /**
-     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit ASCII code.
-     * The final byte is terminated with NULL.
+     * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+     * ASCII code. The final byte is terminated with NULL.
      */
     public static final short TYPE_ASCII = 2;
     /**
@@ -508,13 +47,13 @@
      */
     public static final short TYPE_UNSIGNED_LONG = 4;
     /**
-     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first one is the numerator
-     * and the second one expresses the denominator.
+     * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+     * one is the numerator and the second one expresses the denominator.
      */
     public static final short TYPE_UNSIGNED_RATIONAL = 5;
     /**
-     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any value
-     * depending on the field definition.
+     * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+     * value depending on the field definition.
      */
     public static final short TYPE_UNDEFINED = 7;
     /**
@@ -523,12 +62,18 @@
      */
     public static final short TYPE_LONG = 9;
     /**
-     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first one is the
-     * numerator and the second one is the denominator.
+     * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+     * one is the numerator and the second one is the denominator.
      */
     public static final short TYPE_RATIONAL = 10;
 
+    private static Charset US_ASCII = Charset.forName("US-ASCII");
     private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+    private static final int UNSIGNED_SHORT_MAX = 65535;
+    private static final long UNSIGNED_LONG_MAX = 4294967295L;
+    private static final long LONG_MAX = Integer.MAX_VALUE;
+    private static final long LONG_MIN = Integer.MIN_VALUE;
+
     static {
         TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
         TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
@@ -540,8 +85,57 @@
         TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
     }
 
+    static final int SIZE_UNDEFINED = 0;
+
+    // Exif TagId
+    private final short mTagId;
+    // Exif Tag Type
+    private final short mDataType;
+    // If tag has defined count
+    private boolean mHasDefinedDefaultComponentCount;
+    // Actual data count in tag (should be number of elements in value array)
+    private int mComponentCountActual;
+    // The ifd that this tag should be put in
+    private int mIfd;
+    // The value (array of elements of type Tag Type)
+    private Object mValue;
+    // Value offset in exif header.
+    private int mOffset;
+
+    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+
     /**
-     * Gets the element size of the given data type.
+     * Returns true if the given IFD is a valid IFD.
+     */
+    public static boolean isValidIfd(int ifdId) {
+        return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+                || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+                || ifdId == IfdId.TYPE_IFD_GPS;
+    }
+
+    /**
+     * Returns true if a given type is a valid tag type.
+     */
+    public static boolean isValidType(short type) {
+        return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+                type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+                type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+                type == TYPE_LONG || type == TYPE_RATIONAL;
+    }
+
+    // Use builtTag in ExifInterface instead of constructor.
+    ExifTag(short tagId, short type, int componentCount, int ifd,
+            boolean hasDefinedComponentCount) {
+        mTagId = tagId;
+        mDataType = type;
+        mComponentCountActual = componentCount;
+        mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+        mIfd = ifd;
+        mValue = null;
+    }
+
+    /**
+     * Gets the element size of the given data type in bytes.
      *
      * @see #TYPE_ASCII
      * @see #TYPE_LONG
@@ -556,369 +150,6 @@
         return TYPE_TO_SIZE_MAP[type];
     }
 
-    private static volatile SparseArray<Integer> sTagInfo = null;
-    private static volatile SparseArray<Integer> sInteroperTagInfo = null;
-    private static final int SIZE_UNDEFINED = 0;
-
-    private static SparseArray<Integer> getTagInfo() {
-        if (sTagInfo == null) {
-            synchronized(ExifTag.class) {
-                if (sTagInfo == null) {
-                    sTagInfo = new SparseArray<Integer>();
-                    initTagInfo();
-                }
-            }
-        }
-        return sTagInfo;
-    }
-
-    private static SparseArray<Integer> getInteroperTagInfo() {
-        if (sInteroperTagInfo == null) {
-            synchronized(ExifTag.class) {
-                if (sInteroperTagInfo == null) {
-                    sInteroperTagInfo = new SparseArray<Integer>();
-                    sInteroperTagInfo.put(TAG_INTEROPERABILITY_INDEX,
-                            (IfdId.TYPE_IFD_INTEROPERABILITY << 24)
-                            | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-                }
-            }
-        }
-        return sInteroperTagInfo;
-    }
-
-    private static void initTagInfo() {
-        /**
-         * We put tag information in a 4-bytes integer. The first byte is the
-         * IFD of the tag, and the second byte is the default data type. The
-         * last two byte are a short value indicating the component count of this
-         * tag.
-         */
-        sTagInfo.put(TAG_MAKE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_IMAGE_WIDTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_IMAGE_LENGTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_BITS_PER_SAMPLE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3);
-        sTagInfo.put(TAG_COMPRESSION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_PHOTOMETRIC_INTERPRETATION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_ORIENTATION, (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SAMPLES_PER_PIXEL,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_PLANAR_CONFIGURATION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_Y_CB_CR_SUB_SAMPLING,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
-        sTagInfo.put(TAG_Y_CB_CR_POSITIONING,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_X_RESOLUTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_Y_RESOLUTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_RESOLUTION_UNIT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_STRIP_OFFSETS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ROWS_PER_STRIP,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_STRIP_BYTE_COUNTS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_TRANSFER_FUNCTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
-        sTagInfo.put(TAG_WHITE_POINT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 2);
-        sTagInfo.put(TAG_PRIMARY_CHROMATICITIES,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        sTagInfo.put(TAG_Y_CB_CR_COEFFICIENTS,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_REFERENCE_BLACK_WHITE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6);
-        sTagInfo.put(TAG_DATE_TIME,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_IMAGE_DESCRIPTION,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_MAKE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_MODEL,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SOFTWARE,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ARTIST,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_COPYRIGHT,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_EXIF_IFD,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_GPS_IFD,
-                (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-
-        // EXIF TAG
-        sTagInfo.put(TAG_EXIF_VERSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_FLASHPIX_VERSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_COLOR_SPACE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_COMPONENTS_CONFIGURATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4);
-        sTagInfo.put(TAG_COMPRESSED_BITS_PER_PIXEL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_PIXEL_X_DIMENSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_PIXEL_Y_DIMENSION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1);
-        sTagInfo.put(TAG_MAKER_NOTE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_USER_COMMENT,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_RELATED_SOUND_FILE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 13);
-        sTagInfo.put(TAG_DATE_TIME_ORIGINAL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_DATE_TIME_DIGITIZED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20);
-        sTagInfo.put(TAG_SUB_SEC_TIME,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUB_SEC_TIME_ORIGINAL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUB_SEC_TIME_DIGITIZED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_IMAGE_UNIQUE_ID,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 33);
-        sTagInfo.put(TAG_EXPOSURE_TIME,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_F_NUMBER,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_PROGRAM,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SPECTRAL_SENSITIVITY,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_ISO_SPEED_RATINGS,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_OECF,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SHUTTER_SPEED_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_APERTURE_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_BRIGHTNESS_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_BIAS_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_MAX_APERTURE_VALUE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_DISTANCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_METERING_MODE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_LIGHT_SOURCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FLASH,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_LENGTH,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_AREA,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_FLASH_ENERGY,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SPATIAL_FREQUENCY_RESPONSE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_FOCAL_PLANE_X_RESOLUTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_PLANE_Y_RESOLUTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SUBJECT_LOCATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 2);
-        sTagInfo.put(TAG_EXPOSURE_INDEX,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_SENSING_METHOD,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_FILE_SOURCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
-        sTagInfo.put(TAG_SCENE_TYPE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1);
-        sTagInfo.put(TAG_CFA_PATTERN,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_CUSTOM_RENDERED,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_EXPOSURE_MODE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_WHITE_BALANCE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_DIGITAL_ZOOM_RATIO,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_FOCAL_LENGTH_IN_35_MM_FILE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SCENE_CAPTURE_TYPE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_GAIN_CONTROL,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_CONTRAST,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SATURATION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_SHARPNESS,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        sTagInfo.put(TAG_DEVICE_SETTING_DESCRIPTION,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_SUBJECT_DISTANCE_RANGE,
-                (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1);
-        // GPS tag
-        sTagInfo.put(TAG_GPS_VERSION_ID,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 4);
-        sTagInfo.put(TAG_GPS_LATITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_LONGITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_LATITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_LONGITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_ALTITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 1);
-        sTagInfo.put(TAG_GPS_ALTITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_TIME_STAMP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3);
-        sTagInfo.put(TAG_GPS_SATTELLITES,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_STATUS,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_MEASURE_MODE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DOP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_SPEED_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_SPEED,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_TRACK_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_TRACK,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_IMG_DIRECTION_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_IMG_DIRECTION,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_MAP_DATUM,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_DEST_LATITUDE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_LATITUDE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_DEST_BEARING_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_BEARING,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_DEST_DISTANCE_REF,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2);
-        sTagInfo.put(TAG_GPS_DEST_DISTANCE,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1);
-        sTagInfo.put(TAG_GPS_PROCESSING_METHOD,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_AREA_INFORMATION,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
-        sTagInfo.put(TAG_GPS_DATA_STAMP,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11);
-        sTagInfo.put(TAG_GPS_DIFFERENTIAL,
-                (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11);
-    }
-
-    private final short mTagId;
-    private final short mDataType;
-    private final int mIfd;
-    private final boolean mComponentCountDefined;
-    private int mComponentCount;
-    private Object mValue;
-    private int mOffset;
-
-    static private short getTypeFromInfo(int info) {
-        return (short) ((info >> 16) & 0xff);
-    }
-
-    static private int getComponentCountFromInfo(int info) {
-        return info & 0xffff;
-    }
-
-    static private int getIfdIdFromInfo(int info) {
-        return (info >> 24) & 0xff;
-    }
-
-    static private boolean getComponentCountDefined(short tagId, int ifd) {
-        Integer info = (ifd == IfdId.TYPE_IFD_INTEROPERABILITY) ?
-                getInteroperTagInfo().get(tagId) : getTagInfo().get(tagId);
-        if (info == null) return false;
-        return getComponentCountFromInfo(info) != SIZE_UNDEFINED;
-    }
-
-    static int getIfdIdFromTagId(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null) {
-            throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
-        }
-        return getIfdIdFromInfo(info);
-    }
-
-    /**
-     * Create a tag with given ID. For tags related to interoperability and thumbnail, call
-     * {@link #buildInteroperabilityTag(short)} and {@link #buildThumbnailTag(short)} respectively.
-     * @exception IllegalArgumentException If the ID is invalid.
-     */
-    static public ExifTag buildTag(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null) {
-            throw new IllegalArgumentException("Unknown Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                getIfdIdFromInfo(info));
-    }
-
-    /**
-     * Create a tag related to thumbnail with given ID.
-     * @exception IllegalArgumentException If the ID is invalid.
-     */
-    static public ExifTag buildThumbnailTag(short tagId) {
-        Integer info = getTagInfo().get(tagId);
-        if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_0) {
-            throw new IllegalArgumentException("Unknown Thumnail Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                IfdId.TYPE_IFD_1);
-    }
-
-    /**
-     * Create a tag related to interoperability with given ID.
-     * @exception IllegalArgumentException If the ID is invalid.
-     */
-    static public ExifTag buildInteroperabilityTag(short tagId) {
-        Integer info = getInteroperTagInfo().get(tagId);
-        if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_INTEROPERABILITY) {
-            throw new RuntimeException("Unknown Interoperability Tag ID: " + tagId);
-        }
-        return new ExifTag(tagId, getTypeFromInfo(info),
-                getComponentCountFromInfo(info),
-                IfdId.TYPE_IFD_INTEROPERABILITY);
-    }
-
-    ExifTag(short tagId, short type, int componentCount, int ifd) {
-        mTagId = tagId;
-        mDataType = type;
-        mComponentCount = componentCount;
-        mComponentCountDefined = getComponentCountDefined(tagId, ifd);
-        mIfd = ifd;
-    }
-
     /**
      * Returns the ID of the IFD this tag belongs to.
      *
@@ -932,8 +163,12 @@
         return mIfd;
     }
 
+    protected void setIfd(int ifdId) {
+        mIfd = ifdId;
+    }
+
     /**
-     * Gets the ID of this tag.
+     * Gets the TID of this tag.
      */
     public short getTagId() {
         return mTagId;
@@ -965,48 +200,690 @@
     /**
      * Gets the component count of this tag.
      */
+
+    // TODO: fix integer overflows with this
     public int getComponentCount() {
-        return mComponentCount;
+        return mComponentCountActual;
     }
 
     /**
-     * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value
-     * that links to the area where the actual value is located.
-     *
-     * @see #getOffset()
+     * Sets the component count of this tag. Call this function before
+     * setValue() if the length of value does not match the component count.
+     */
+    protected void forceSetComponentCount(int count) {
+        mComponentCountActual = count;
+    }
+
+    /**
+     * Returns true if this ExifTag contains value; otherwise, this tag will
+     * contain an offset value that is determined when the tag is written.
      */
     public boolean hasValue() {
         return mValue != null;
     }
 
     /**
-     * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset
-     * to the location of the actual value.
+     * Sets integer values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
      */
-    public int getOffset() {
+    public boolean setValue(int[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+                mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+            return false;
+        } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+
+        long[] data = new long[value.length];
+        for (int i = 0; i < value.length; i++) {
+            data[i] = value[i];
+        }
+        mValue = data;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets integer value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition of this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(int value) {
+        return setValue(new int[] {
+                value
+        });
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(long[] value) {
+        if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+            return false;
+        }
+        if (checkOverflowForUnsignedLong(value)) {
+            return false;
+        }
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets long values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(long value) {
+        return setValue(new long[] {
+                value
+        });
+    }
+
+    /**
+     * Sets a string value into this tag. This method should be used for tags of
+     * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+     * Characters that cannot be converted are replaced with '?'. The length of
+     * the string must be equal to either (component count -1) or (component
+     * count). The final byte will be set to the string null terminator '\0',
+     * overwriting the last character in the string if the value.length is equal
+     * to the component count. This method will fail if:
+     * <ul>
+     * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+     * <li>The length of the string is not equal to (component count -1) or
+     * (component count) in the definition for this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(String value) {
+        if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+
+        byte[] buf = value.getBytes(US_ASCII);
+        byte[] finalBuf = buf;
+        if (buf.length > 0) {
+            finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+                .copyOf(buf, buf.length + 1);
+        } else if (mDataType == TYPE_ASCII && mComponentCountActual == 1) {
+            finalBuf = new byte[] { 0 };
+        }
+        int count = finalBuf.length;
+        if (checkBadComponentCount(count)) {
+            return false;
+        }
+        mComponentCountActual = count;
+        mValue = finalBuf;
+        return true;
+    }
+
+    /**
+     * Sets Rational values into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The value.length does NOT match the component count in the definition
+     * for this tag.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational[] value) {
+        if (checkBadComponentCount(value.length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+            return false;
+        }
+        if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+            return false;
+        } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+            return false;
+        }
+
+        mValue = value;
+        mComponentCountActual = value.length;
+        return true;
+    }
+
+    /**
+     * Sets a Rational value into this tag. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+     * method will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+     * or {@link #TYPE_RATIONAL}.</li>
+     * <li>The value overflows.</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     *
+     * @see Rational
+     */
+    public boolean setValue(Rational value) {
+        return setValue(new Rational[] {
+                value
+        });
+    }
+
+    /**
+     * Sets byte values into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The length does NOT match the component count in the definition for
+     * this tag.</li>
+     * </ul>
+     */
+    public boolean setValue(byte[] value, int offset, int length) {
+        if (checkBadComponentCount(length)) {
+            return false;
+        }
+        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+            return false;
+        }
+        mValue = new byte[length];
+        System.arraycopy(value, offset, mValue, 0, length);
+        mComponentCountActual = length;
+        return true;
+    }
+
+    /**
+     * Equivalent to setValue(value, 0, value.length).
+     */
+    public boolean setValue(byte[] value) {
+        return setValue(value, 0, value.length);
+    }
+
+    /**
+     * Sets byte value into this tag. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+     * will fail if:
+     * <ul>
+     * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+     * {@link #TYPE_UNDEFINED} .</li>
+     * <li>The component count in the definition for this tag is not 1.</li>
+     * </ul>
+     */
+    public boolean setValue(byte value) {
+        return setValue(new byte[] {
+                value
+        });
+    }
+
+    /**
+     * Sets the value for this tag using an appropriate setValue method for the
+     * given object. This method will fail if:
+     * <ul>
+     * <li>The corresponding setValue method for the class of the object passed
+     * in would fail.</li>
+     * <li>There is no obvious way to cast the object passed in into an EXIF tag
+     * type.</li>
+     * </ul>
+     */
+    public boolean setValue(Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (obj instanceof Short) {
+            return setValue(((Short) obj).shortValue() & 0x0ffff);
+        } else if (obj instanceof String) {
+            return setValue((String) obj);
+        } else if (obj instanceof int[]) {
+            return setValue((int[]) obj);
+        } else if (obj instanceof long[]) {
+            return setValue((long[]) obj);
+        } else if (obj instanceof Rational) {
+            return setValue((Rational) obj);
+        } else if (obj instanceof Rational[]) {
+            return setValue((Rational[]) obj);
+        } else if (obj instanceof byte[]) {
+            return setValue((byte[]) obj);
+        } else if (obj instanceof Integer) {
+            return setValue(((Integer) obj).intValue());
+        } else if (obj instanceof Long) {
+            return setValue(((Long) obj).longValue());
+        } else if (obj instanceof Byte) {
+            return setValue(((Byte) obj).byteValue());
+        } else if (obj instanceof Short[]) {
+            // Nulls in this array are treated as zeroes.
+            Short[] arr = (Short[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+            }
+            return setValue(fin);
+        } else if (obj instanceof Integer[]) {
+            // Nulls in this array are treated as zeroes.
+            Integer[] arr = (Integer[]) obj;
+            int[] fin = new int[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Long[]) {
+            // Nulls in this array are treated as zeroes.
+            Long[] arr = (Long[]) obj;
+            long[] fin = new long[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+            }
+            return setValue(fin);
+        } else if (obj instanceof Byte[]) {
+            // Nulls in this array are treated as zeroes.
+            Byte[] arr = (Byte[]) obj;
+            byte[] fin = new byte[arr.length];
+            for (int i = 0; i < arr.length; i++) {
+                fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+            }
+            return setValue(fin);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Sets a timestamp to this tag. The method converts the timestamp with the
+     * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+     * method will fail if the data type is not {@link #TYPE_ASCII} or the
+     * component count of this tag is not 20 or undefined.
+     *
+     * @param time the number of milliseconds since Jan. 1, 1970 GMT
+     * @return true on success
+     */
+    public boolean setTimeValue(long time) {
+        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+        synchronized (TIME_FORMAT) {
+            return setValue(TIME_FORMAT.format(new Date(time)));
+        }
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @return the value as a String, or null if the tag's value does not exist
+     *         or cannot be converted to a String.
+     */
+    public String getValueAsString() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof String) {
+            return (String) mValue;
+        } else if (mValue instanceof byte[]) {
+            return new String((byte[]) mValue, US_ASCII);
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a String. This method should be used for tags of type
+     * {@link #TYPE_ASCII}.
+     *
+     * @param defaultValue the String to return if the tag's value does not
+     *            exist or cannot be converted to a String.
+     * @return the tag's value as a String, or the defaultValue.
+     */
+    public String getValueAsString(String defaultValue) {
+        String s = getValueAsString();
+        if (s == null) {
+            return defaultValue;
+        }
+        return s;
+    }
+
+    /**
+     * Gets the value as a byte array. This method should be used for tags of
+     * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @return the value as a byte array, or null if the tag's value does not
+     *         exist or cannot be converted to a byte array.
+     */
+    public byte[] getValueAsBytes() {
+        if (mValue instanceof byte[]) {
+            return (byte[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a byte. If there are more than 1 bytes in this value,
+     * gets the first byte. This method should be used for tags of type
+     * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     *
+     * @param defaultValue the byte to return if tag's value does not exist or
+     *            cannot be converted to a byte.
+     * @return the tag's value as a byte, or the defaultValue.
+     */
+    public byte getValueAsByte(byte defaultValue) {
+        byte[] b = getValueAsBytes();
+        if (b == null || b.length < 1) {
+            return defaultValue;
+        }
+        return b[0];
+    }
+
+    /**
+     * Gets the value as an array of Rationals. This method should be used for
+     * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @return the value as as an array of Rationals, or null if the tag's value
+     *         does not exist or cannot be converted to an array of Rationals.
+     */
+    public Rational[] getValueAsRationals() {
+        if (mValue instanceof Rational[]) {
+            return (Rational[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the Rational to return if tag's value does not exist
+     *            or cannot be converted to a Rational.
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(Rational defaultValue) {
+        Rational[] r = getValueAsRationals();
+        if (r == null || r.length < 1) {
+            return defaultValue;
+        }
+        return r[0];
+    }
+
+    /**
+     * Gets the value as a Rational. If there are more than 1 Rationals in this
+     * value, gets the first one. This method should be used for tags of type
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     *
+     * @param defaultValue the numerator of the Rational to return if tag's
+     *            value does not exist or cannot be converted to a Rational (the
+     *            denominator will be 1).
+     * @return the tag's value as a Rational, or the defaultValue.
+     */
+    public Rational getValueAsRational(long defaultValue) {
+        Rational defaultVal = new Rational(defaultValue, 1);
+        return getValueAsRational(defaultVal);
+    }
+
+    /**
+     * Gets the value as an array of ints. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of ints, or null if the tag's value does
+     *         not exist or cannot be converted to an array of ints.
+     */
+    public int[] getValueAsInts() {
+        if (mValue == null) {
+            return null;
+        } else if (mValue instanceof long[]) {
+            long[] val = (long[]) mValue;
+            int[] arr = new int[val.length];
+            for (int i = 0; i < val.length; i++) {
+                arr[i] = (int) val[i]; // Truncates
+            }
+            return arr;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value as an int. If there are more than 1 ints in this value,
+     * gets the first one. This method should be used for tags of type
+     * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the int to return if tag's value does not exist or
+     *            cannot be converted to an int.
+     * @return the tag's value as a int, or the defaultValue.
+     */
+    public int getValueAsInt(int defaultValue) {
+        int[] i = getValueAsInts();
+        if (i == null || i.length < 1) {
+            return defaultValue;
+        }
+        return i[0];
+    }
+
+    /**
+     * Gets the value as an array of longs. This method should be used for tags
+     * of type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @return the value as as an array of longs, or null if the tag's value
+     *         does not exist or cannot be converted to an array of longs.
+     */
+    public long[] getValueAsLongs() {
+        if (mValue instanceof long[]) {
+            return (long[]) mValue;
+        }
+        return null;
+    }
+
+    /**
+     * Gets the value or null if none exists. If there are more than 1 longs in
+     * this value, gets the first one. This method should be used for tags of
+     * type {@link #TYPE_UNSIGNED_LONG}.
+     *
+     * @param defaultValue the long to return if tag's value does not exist or
+     *            cannot be converted to a long.
+     * @return the tag's value as a long, or the defaultValue.
+     */
+    public long getValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l == null || l.length < 1) {
+            return defaultValue;
+        }
+        return l[0];
+    }
+
+    /**
+     * Gets the tag's value or null if none exists.
+     */
+    public Object getValue() {
+        return mValue;
+    }
+
+    /**
+     * Gets a long representation of the value.
+     *
+     * @param defaultValue value to return if there is no value or value is a
+     *            rational with a denominator of 0.
+     * @return the tag's value as a long, or defaultValue if no representation
+     *         exists.
+     */
+    public long forceGetValueAsLong(long defaultValue) {
+        long[] l = getValueAsLongs();
+        if (l != null && l.length >= 1) {
+            return l[0];
+        }
+        byte[] b = getValueAsBytes();
+        if (b != null && b.length >= 1) {
+            return b[0];
+        }
+        Rational[] r = getValueAsRationals();
+        if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+            return (long) r[0].toDouble();
+        }
+        return defaultValue;
+    }
+
+    /**
+     * Gets a string representation of the value.
+     */
+    public String forceGetValueAsString() {
+        if (mValue == null) {
+            return "";
+        } else if (mValue instanceof byte[]) {
+            if (mDataType == TYPE_ASCII) {
+                return new String((byte[]) mValue, US_ASCII);
+            } else {
+                return Arrays.toString((byte[]) mValue);
+            }
+        } else if (mValue instanceof long[]) {
+            if (((long[]) mValue).length == 1) {
+                return String.valueOf(((long[]) mValue)[0]);
+            } else {
+                return Arrays.toString((long[]) mValue);
+            }
+        } else if (mValue instanceof Object[]) {
+            if (((Object[]) mValue).length == 1) {
+                Object val = ((Object[]) mValue)[0];
+                if (val == null) {
+                    return "";
+                } else {
+                    return val.toString();
+                }
+            } else {
+                return Arrays.toString((Object[]) mValue);
+            }
+        } else {
+            return mValue.toString();
+        }
+    }
+
+    /**
+     * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+     * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+     * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+     * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+     * {@link #getRational(int)} instead.
+     *
+     * @exception IllegalArgumentException if the data type is
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected long getValueAt(int index) {
+        if (mValue instanceof long[]) {
+            return ((long[]) mValue)[index];
+        } else if (mValue instanceof byte[]) {
+            return ((byte[]) mValue)[index];
+        }
+        throw new IllegalArgumentException("Cannot get integer value from "
+                + convertTypeToString(mDataType));
+    }
+
+    /**
+     * Gets the {@link #TYPE_ASCII} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_ASCII}.
+     */
+    protected String getString() {
+        if (mDataType != TYPE_ASCII) {
+            throw new IllegalArgumentException("Cannot get ASCII value from "
+                    + convertTypeToString(mDataType));
+        }
+        return new String((byte[]) mValue, US_ASCII);
+    }
+
+    /*
+     * Get the converted ascii byte. Used by ExifOutputStream.
+     */
+    protected byte[] getStringByte() {
+        return (byte[]) mValue;
+    }
+
+    /**
+     * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+     *
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+     */
+    protected Rational getRational(int index) {
+        if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
+            throw new IllegalArgumentException("Cannot get RATIONAL value from "
+                    + convertTypeToString(mDataType));
+        }
+        return ((Rational[]) mValue)[index];
+    }
+
+    /**
+     * Equivalent to getBytes(buffer, 0, buffer.length).
+     */
+    protected void getBytes(byte[] buf) {
+        getBytes(buf, 0, buf.length);
+    }
+
+    /**
+     * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+     *
+     * @param buf the byte array in which to store the bytes read.
+     * @param offset the initial position in buffer to store the bytes.
+     * @param length the maximum number of bytes to store in buffer. If length >
+     *            component count, only the valid bytes will be stored.
+     * @exception IllegalArgumentException If the type is NOT
+     *                {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+     */
+    protected void getBytes(byte[] buf, int offset, int length) {
+        if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
+            throw new IllegalArgumentException("Cannot get BYTE value from "
+                    + convertTypeToString(mDataType));
+        }
+        System.arraycopy(mValue, 0, buf, offset,
+                (length > mComponentCountActual) ? mComponentCountActual : length);
+    }
+
+    /**
+     * Gets the offset of this tag. This is only valid if this data size > 4 and
+     * contains an offset to the location of the actual value.
+     */
+    protected int getOffset() {
         return mOffset;
     }
 
     /**
      * Sets the offset of this tag.
      */
-    void setOffset(int offset) {
+    protected void setOffset(int offset) {
         mOffset = offset;
     }
 
-    private void checkComponentCountOrThrow(int count)
-            throws IllegalArgumentException {
-        if (mComponentCountDefined && (mComponentCount != count)) {
-            throw new IllegalArgumentException("Tag " + mTagId + ": Required "
-                    + mComponentCount + " components but was given " + count
-                    + " component(s)");
-        }
+    protected void setHasDefinedCount(boolean d) {
+        mHasDefinedDefaultComponentCount = d;
     }
 
-    private void throwTypeNotMatchedException(String className)
-            throws IllegalArgumentException {
-        throw new IllegalArgumentException("Tag " + mTagId + ": expect type " +
-                convertTypeToString(mDataType) + " but got " + className);
+    protected boolean hasDefinedCount() {
+        return mHasDefinedDefaultComponentCount;
+    }
+
+    private boolean checkBadComponentCount(int count) {
+        if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+            return true;
+        }
+        return false;
     }
 
     private static String convertTypeToString(short type) {
@@ -1032,402 +909,84 @@
         }
     }
 
-    private static final int UNSIGNED_SHORT_MAX = 65535;
-    private static final long UNSIGNED_LONG_MAX = 4294967295L;
-    private static final long LONG_MAX = Integer.MAX_VALUE;
-    private static final long LONG_MIN = Integer.MIN_VALUE;
-
-    private void checkOverflowForUnsignedShort(int[] value) {
+    private boolean checkOverflowForUnsignedShort(int[] value) {
         for (int v : value) {
             if (v > UNSIGNED_SHORT_MAX || v < 0) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_SHORT");
+                return true;
             }
         }
+        return false;
     }
 
-    private void checkOverflowForUnsignedLong(long[] value) {
-        for (long v: value) {
+    private boolean checkOverflowForUnsignedLong(long[] value) {
+        for (long v : value) {
             if (v < 0 || v > UNSIGNED_LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_LONG");
+                return true;
             }
         }
+        return false;
     }
 
-    private void checkOverflowForUnsignedLong(int[] value) {
-        for (int v: value) {
+    private boolean checkOverflowForUnsignedLong(int[] value) {
+        for (int v : value) {
             if (v < 0) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_LONG");
+                return true;
             }
         }
+        return false;
     }
 
-    private void checkOverflowForUnsignedRational(Rational[] value) {
-        for (Rational v: value) {
-            if (v.getNominator() < 0 || v.getDenominator() < 0
-                    || v.getNominator() > UNSIGNED_LONG_MAX
+    private boolean checkOverflowForUnsignedRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < 0 || v.getDenominator() < 0
+                    || v.getNumerator() > UNSIGNED_LONG_MAX
                     || v.getDenominator() > UNSIGNED_LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type UNSIGNED_RATIONAL");
+                return true;
             }
         }
+        return false;
     }
 
-    private void checkOverflowForRational(Rational[] value) {
-        for (Rational v: value) {
-            if (v.getNominator() < LONG_MIN || v.getDenominator() < LONG_MIN
-                    || v.getNominator() > LONG_MAX
+    private boolean checkOverflowForRational(Rational[] value) {
+        for (Rational v : value) {
+            if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+                    || v.getNumerator() > LONG_MAX
                     || v.getDenominator() > LONG_MAX) {
-                throw new IllegalArgumentException(
-                        "Tag " + mTagId+ ": Value" + v +
-                        " is illegal for type RATIONAL");
+                return true;
             }
         }
-    }
-
-    /**
-     * Sets integer values into this tag.
-     * @exception IllegalArgumentException for the following situation:
-     * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     *      {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     *     <li>The value overflows. </li>
-     *     <li>The value.length does NOT match the definition of component count in
-     *      EXIF standard.</li>
-     * </ul>
-     */
-    public void setValue(int[] value) {
-        checkComponentCountOrThrow(value.length);
-        if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
-                mDataType != TYPE_UNSIGNED_LONG) {
-            throwTypeNotMatchedException("int");
-        }
-        if (mDataType == TYPE_UNSIGNED_SHORT) {
-            checkOverflowForUnsignedShort(value);
-        } else if (mDataType == TYPE_UNSIGNED_LONG) {
-            checkOverflowForUnsignedLong(value);
-        }
-
-        long[] data = new long[value.length];
-        for (int i = 0; i < value.length; i++) {
-            data[i] = value[i];
-        }
-        mValue = data;
-        mComponentCount = value.length;
-    }
-
-    /**
-     * Sets integer values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
-     *      {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li>
-     *     <li>The value overflows.</li>
-     *     <li>The component count in the definition of EXIF standard is not 1.</li>
-     * </ul>
-     */
-    public void setValue(int value) {
-        checkComponentCountOrThrow(1);
-        setValue(new int[] {value});
-    }
-
-    /**
-     * Sets long values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     *      <li>The value overflows. </li>
-     *      <li>The value.length does NOT match the definition of component count in
-     *       EXIF standard.</li>
-     * </ul>
-     */
-    public void setValue(long[] value) {
-        checkComponentCountOrThrow(value.length);
-        if (mDataType != TYPE_UNSIGNED_LONG) {
-            throwTypeNotMatchedException("long");
-        }
-        checkOverflowForUnsignedLong(value);
-        mValue = value;
-        mComponentCount = value.length;
-    }
-
-    /**
-     * Sets long values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *     <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li>
-     *     <li>The value overflows. </li>
-     *     <li>The component count in the definition of EXIF standard is not 1.</li>
-     * </ul>
-     */
-    public void setValue(long value) {
-        setValue(new long[] {value});
-    }
-
-    /**
-     * Sets string values into this tag.
-     * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
-     * or value.length() + 1 does NOT fit the definition of the component count in the
-     * EXIF standard.
-     */
-    public void setValue(String value) {
-        checkComponentCountOrThrow(value.length() + 1);
-        if (mDataType != TYPE_ASCII) {
-            throwTypeNotMatchedException("String");
-        }
-        mComponentCount = value.length() + 1;
-        mValue = value;
-    }
-
-    /**
-     * Sets Rational values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
-     *       {@link #TYPE_RATIONAL} .</li>
-     *      <li>The value overflows. </li>
-     *      <li>The value.length does NOT match the definition of component count in
-     *       EXIF standard.</li>
-     * </ul>
-     */
-    public void setValue(Rational[] value) {
-        if (mDataType == TYPE_UNSIGNED_RATIONAL) {
-            checkOverflowForUnsignedRational(value);
-        } else if (mDataType == TYPE_RATIONAL) {
-            checkOverflowForRational(value);
-        } else {
-            throwTypeNotMatchedException("Rational");
-        }
-        checkComponentCountOrThrow(value.length);
-        mValue = value;
-        mComponentCount = value.length;
-    }
-
-    /**
-     * Sets Rational values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or
-     *       {@link #TYPE_RATIONAL} .</li>
-     *      <li>The value overflows. </li>
-     *      <li>The component count in the definition of EXIF standard is not 1.</li>
-     * </ul>
-     * */
-    public void setValue(Rational value) {
-        setValue(new Rational[] {value});
-    }
-
-    /**
-     * Sets byte values into this tag.
-     * @exception IllegalArgumentException For the following situation:
-     * <ul>
-     *      <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
-     *       {@link #TYPE_UNDEFINED} .</li>
-     *      <li>The length does NOT match the definition of component count in EXIF standard.</li>
-     * </ul>
-     * */
-    public void setValue(byte[] value, int offset, int length) {
-        checkComponentCountOrThrow(length);
-        if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
-            throwTypeNotMatchedException("byte");
-        }
-        mValue = new byte[length];
-        System.arraycopy(value, offset, mValue, 0, length);
-        mComponentCount = length;
-    }
-
-    /**
-     * Equivalent to setValue(value, 0, value.length).
-     */
-    public void setValue(byte[] value) {
-        setValue(value, 0, value.length);
-    }
-
-    private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
-
-    /**
-     * Sets a timestamp to this tag. The method converts the timestamp with the format of
-     * "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}.
-     *
-     * @param time the number of milliseconds since Jan. 1, 1970 GMT
-     * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
-     * or the component count of this tag is not 20 or undefined
-     */
-    public void setTimeValue(long time) {
-        // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
-        synchronized (TIME_FORMAT) {
-            setValue(TIME_FORMAT.format(new Date(time)));
-        }
-    }
-
-    /**
-     * Gets the {@link #TYPE_UNSIGNED_SHORT} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}.
-     */
-    public int getUnsignedShort(int index) {
-        if (mDataType != TYPE_UNSIGNED_SHORT) {
-            throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from "
-                    + convertTypeToString(mDataType));
-        }
-        return (int) (((long[]) mValue) [index]);
-    }
-
-    /**
-     * Gets the {@link #TYPE_LONG} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}.
-     */
-    public int getLong(int index) {
-        if (mDataType != TYPE_LONG) {
-            throw new IllegalArgumentException("Cannot get LONG value from "
-                    + convertTypeToString(mDataType));
-        }
-        return (int) (((long[]) mValue) [index]);
-    }
-
-    /**
-     * Gets the {@link #TYPE_UNSIGNED_LONG} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}.
-     */
-    public long getUnsignedLong(int index) {
-        if (mDataType != TYPE_UNSIGNED_LONG) {
-            throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from "
-                    + convertTypeToString(mDataType));
-        }
-        return ((long[]) mValue) [index];
-    }
-
-    /**
-     * Gets the {@link #TYPE_ASCII} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}.
-     */
-    public String getString() {
-        if (mDataType != TYPE_ASCII) {
-            throw new IllegalArgumentException("Cannot get ASCII value from "
-                    + convertTypeToString(mDataType));
-        }
-        return (String) mValue;
-    }
-
-    /**
-     * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_RATIONAL} or
-     * {@link #TYPE_UNSIGNED_RATIONAL}.
-     */
-    public Rational getRational(int index) {
-        if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
-            throw new IllegalArgumentException("Cannot get RATIONAL value from "
-                    + convertTypeToString(mDataType));
-        }
-        return ((Rational[]) mValue) [index];
-    }
-
-    /**
-     * Equivalent to getBytes(buffer, 0, buffer.length).
-     */
-    public void getBytes(byte[] buf) {
-        getBytes(buf, 0, buf.length);
-    }
-
-    /**
-     * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
-     *
-     * @param buf the byte array in which to store the bytes read.
-     * @param offset the initial position in buffer to store the bytes.
-     * @param length the maximum number of bytes to store in buffer. If length > component count,
-     * only the valid bytes will be stored.
-     *
-     * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNDEFINED} or
-     * {@link #TYPE_UNSIGNED_BYTE}.
-     */
-    public void getBytes(byte[] buf, int offset, int length) {
-        if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) {
-            throw new IllegalArgumentException("Cannot get BYTE value from "
-                    + convertTypeToString(mDataType));
-        }
-        System.arraycopy(mValue, 0, buf, offset,
-                (length > mComponentCount) ? mComponentCount : length);
-    }
-
-    /**
-     * Returns a string representation of the value of this tag.
-     */
-    public String valueToString() {
-        StringBuilder sbuilder = new StringBuilder();
-        switch (getDataType()) {
-            case ExifTag.TYPE_UNDEFINED:
-            case ExifTag.TYPE_UNSIGNED_BYTE:
-                byte buf[] = new byte[getComponentCount()];
-                getBytes(buf);
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(String.format("%02x", buf[i]));
-                }
-                break;
-            case ExifTag.TYPE_ASCII:
-                sbuilder.append(getString());
-                break;
-            case ExifTag.TYPE_UNSIGNED_LONG:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getUnsignedLong(i));
-                }
-                break;
-            case ExifTag.TYPE_RATIONAL:
-            case ExifTag.TYPE_UNSIGNED_RATIONAL:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    Rational r = getRational(i);
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
-                }
-                break;
-            case ExifTag.TYPE_UNSIGNED_SHORT:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getUnsignedShort(i));
-                }
-                break;
-            case ExifTag.TYPE_LONG:
-                for(int i = 0, n = getComponentCount(); i < n; i++) {
-                    if(i != 0) sbuilder.append(" ");
-                    sbuilder.append(getLong(i));
-                }
-                break;
-        }
-        return sbuilder.toString();
-    }
-
-    /**
-     * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
-     * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
-     * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
-     */
-    static boolean isOffsetTag(short tagId) {
-        return tagId == TAG_EXIF_IFD
-                || tagId == TAG_GPS_IFD
-                || tagId == TAG_JPEG_INTERCHANGE_FORMAT
-                || tagId == TAG_STRIP_OFFSETS
-                || tagId == TAG_INTEROPERABILITY_IFD;
+        return false;
     }
 
     @Override
     public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
         if (obj instanceof ExifTag) {
             ExifTag tag = (ExifTag) obj;
+            if (tag.mTagId != this.mTagId
+                    || tag.mComponentCountActual != this.mComponentCountActual
+                    || tag.mDataType != this.mDataType) {
+                return false;
+            }
             if (mValue != null) {
-                if (mValue instanceof long[]) {
-                    if (!(tag.mValue instanceof long[])) return false;
+                if (tag.mValue == null) {
+                    return false;
+                } else if (mValue instanceof long[]) {
+                    if (!(tag.mValue instanceof long[])) {
+                        return false;
+                    }
                     return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
                 } else if (mValue instanceof Rational[]) {
-                    if (!(tag.mValue instanceof Rational[])) return false;
+                    if (!(tag.mValue instanceof Rational[])) {
+                        return false;
+                    }
                     return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue);
                 } else if (mValue instanceof byte[]) {
-                    if (!(tag.mValue instanceof byte[])) return false;
+                    if (!(tag.mValue instanceof byte[])) {
+                        return false;
+                    }
                     return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
                 } else {
                     return mValue.equals(tag.mValue);
@@ -1438,4 +997,12 @@
         }
         return false;
     }
+
+    @Override
+    public String toString() {
+        return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+                + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+                + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+    }
+
 }
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
index 78f9173..093944a 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
@@ -30,7 +30,10 @@
     private final int mIfdId;
     private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
     private int mOffsetToNextIfd = 0;
-
+    private static final int[] sIfds = {
+            IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+            IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+    };
     /**
      * Creates an IfdData with given IFD ID.
      *
@@ -40,14 +43,18 @@
      * @see IfdId#TYPE_IFD_GPS
      * @see IfdId#TYPE_IFD_INTEROPERABILITY
      */
-    public IfdData(int ifdId) {
+    IfdData(int ifdId) {
         mIfdId = ifdId;
     }
 
+    static protected int[] getIfds() {
+        return sIfds;
+    }
+
     /**
      * Get a array the contains all {@link ExifTag} in this IFD.
      */
-    public ExifTag[] getAllTags() {
+    protected ExifTag[] getAllTags() {
         return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
     }
 
@@ -60,63 +67,86 @@
      * @see IfdId#TYPE_IFD_GPS
      * @see IfdId#TYPE_IFD_INTEROPERABILITY
      */
-    public int getId() {
+    protected int getId() {
         return mIfdId;
     }
 
     /**
-     * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag.
+     * Gets the {@link ExifTag} with given tag id. Return null if there is no
+     * such tag.
      */
-    public ExifTag getTag(short tagId) {
+    protected ExifTag getTag(short tagId) {
         return mExifTags.get(tagId);
     }
 
     /**
      * Adds or replaces a {@link ExifTag}.
      */
-    public void setTag(ExifTag tag) {
-        mExifTags.put(tag.getTagId(), tag);
+    protected ExifTag setTag(ExifTag tag) {
+        tag.setIfd(mIfdId);
+        return mExifTags.put(tag.getTagId(), tag);
+    }
+
+    protected boolean checkCollision(short tagId) {
+        return mExifTags.get(tagId) != null;
+    }
+
+    /**
+     * Removes the tag of the given ID
+     */
+    protected void removeTag(short tagId) {
+        mExifTags.remove(tagId);
     }
 
     /**
      * Gets the tags count in the IFD.
      */
-    public int getTagCount() {
+    protected int getTagCount() {
         return mExifTags.size();
     }
 
     /**
      * Sets the offset of next IFD.
      */
-    void setOffsetToNextIfd(int offset) {
+    protected void setOffsetToNextIfd(int offset) {
         mOffsetToNextIfd = offset;
     }
 
     /**
      * Gets the offset of next IFD.
      */
-    int getOffsetToNextIfd() {
+    protected int getOffsetToNextIfd() {
         return mOffsetToNextIfd;
     }
 
     /**
-     * Returns true if all tags in this two IFDs are equal. Note that tags of IFDs offset or
-     * thumbnail offset will be ignored.
+     * Returns true if all tags in this two IFDs are equal. Note that tags of
+     * IFDs offset or thumbnail offset will be ignored.
      */
     @Override
     public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
         if (obj instanceof IfdData) {
             IfdData data = (IfdData) obj;
             if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
                 ExifTag[] tags = data.getAllTags();
-                for (ExifTag tag: tags) {
-                    if (ExifTag.isOffsetTag(tag.getTagId())) continue;
+                for (ExifTag tag : tags) {
+                    if (ExifInterface.isOffsetTag(tag.getTagId())) {
+                        continue;
+                    }
                     ExifTag tag2 = mExifTags.get(tag.getTagId());
-                    if (!tag.equals(tag2)) return false;
+                    if (!tag.equals(tag2)) {
+                        return false;
+                    }
                 }
                 return true;
             }
         }
         return false;
     }
-}
\ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
index 1b96343..7842edb 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
@@ -13,14 +13,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.android.gallery3d.exif;
 
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
 public interface IfdId {
     public static final int TYPE_IFD_0 = 0;
     public static final int TYPE_IFD_1 = 1;
     public static final int TYPE_IFD_EXIF = 2;
     public static final int TYPE_IFD_INTEROPERABILITY = 3;
     public static final int TYPE_IFD_GPS = 4;
-    /* This is use in ExifData to allocate enough IfdData */
+    /* This is used in ExifData to allocate enough IfdData */
     static final int TYPE_IFD_COUNT = 5;
+
 }
diff --git a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
index 4f785a8..428e6b9 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -29,24 +29,28 @@
         super(out);
     }
 
-    public void setByteOrder(ByteOrder order) {
+    public OrderedDataOutputStream setByteOrder(ByteOrder order) {
         mByteBuffer.order(order);
+        return this;
     }
 
-    public void writeShort(short value) throws IOException {
+    public OrderedDataOutputStream writeShort(short value) throws IOException {
         mByteBuffer.rewind();
         mByteBuffer.putShort(value);
         out.write(mByteBuffer.array(), 0, 2);
-     }
+        return this;
+    }
 
-    public void writeInt(int value) throws IOException {
+    public OrderedDataOutputStream writeInt(int value) throws IOException {
         mByteBuffer.rewind();
         mByteBuffer.putInt(value);
         out.write(mByteBuffer.array());
+        return this;
     }
 
-    public void writeRational(Rational rational) throws IOException {
-        writeInt((int) rational.getNominator());
+    public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+        writeInt((int) rational.getNumerator());
         writeInt((int) rational.getDenominator());
+        return this;
     }
 }
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
index 7d90262..591d63f 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/Rational.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
@@ -16,30 +16,73 @@
 
 package com.android.gallery3d.exif;
 
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
 public class Rational {
 
-    private final long mNominator;
+    private final long mNumerator;
     private final long mDenominator;
 
+    /**
+     * Create a Rational with a given numerator and denominator.
+     *
+     * @param nominator
+     * @param denominator
+     */
     public Rational(long nominator, long denominator) {
-        mNominator = nominator;
+        mNumerator = nominator;
         mDenominator = denominator;
     }
 
-    public long getNominator() {
-        return mNominator;
+    /**
+     * Create a copy of a Rational.
+     */
+    public Rational(Rational r) {
+        mNumerator = r.mNumerator;
+        mDenominator = r.mDenominator;
     }
 
+    /**
+     * Gets the numerator of the rational.
+     */
+    public long getNumerator() {
+        return mNumerator;
+    }
+
+    /**
+     * Gets the denominator of the rational
+     */
     public long getDenominator() {
         return mDenominator;
     }
 
+    /**
+     * Gets the rational value as type double. Will cause a divide-by-zero error
+     * if the denominator is 0.
+     */
+    public double toDouble() {
+        return mNumerator / (double) mDenominator;
+    }
+
     @Override
     public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (this == obj) {
+            return true;
+        }
         if (obj instanceof Rational) {
             Rational data = (Rational) obj;
-            return mNominator == data.mNominator && mDenominator == data.mDenominator;
+            return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
         }
         return false;
     }
-}
\ No newline at end of file
+
+    @Override
+    public String toString() {
+        return mNumerator + "/" + mDenominator;
+    }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Util.java b/gallerycommon/src/com/android/gallery3d/exif/Util.java
deleted file mode 100644
index 594d6fc..0000000
--- a/gallerycommon/src/com/android/gallery3d/exif/Util.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.exif;
-
-import java.io.Closeable;
-
-class Util {
-    public static boolean equals(Object a, Object b) {
-        return (a == b) || (a == null ? false : a.equals(b));
-    }
-
-    public static void closeSilently(Closeable c) {
-        if (c == null) return;
-        try {
-            c.close();
-        } catch (Throwable t) {
-            // do nothing
-        }
-    }
-}
diff --git a/jni/Android.mk b/jni/Android.mk
index db31bfd..e612486 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -25,23 +25,26 @@
 LOCAL_LDFLAGS	:= -llog -ljnigraphics
 LOCAL_SDK_VERSION := 9
 LOCAL_MODULE    := libjni_filtershow_filters
-LOCAL_SRC_FILES := filters/bw.c \
-                   filters/gradient.c \
+LOCAL_SRC_FILES := filters/gradient.c \
                    filters/saturated.c \
                    filters/exposure.c \
+                   filters/edge.c \
                    filters/contrast.c \
                    filters/hue.c \
                    filters/shadows.c \
+                   filters/highlight.c \
                    filters/hsv.c \
                    filters/vibrance.c \
                    filters/geometry.c \
+                   filters/negative.c \
                    filters/vignette.c \
                    filters/redEyeMath.c \
                    filters/fx.c \
                    filters/wbalance.c \
                    filters/redeye.c \
                    filters/bwfilter.c \
-                   filters/tinyplanet.cc
+                   filters/tinyplanet.cc \
+                   filters/kmeans.cc
 
 LOCAL_CFLAGS    += -ffast-math -O3 -funroll-loops
 LOCAL_ARM_MODE := arm
diff --git a/jni/filters/bw.c b/jni/filters/bw.c
deleted file mode 100644
index f075d30..0000000
--- a/jni/filters/bw.c
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "filters.h"
-
-void JNIFUNCF(ImageFilterBW, nativeApplyFilter, jobject bitmap, jint width, jint height)
-{
-    char* destination = 0;
-    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
-    int i;
-    int len = width * height * 4;
-    float Rf = 0.2999f;
-    float Gf = 0.587f;
-    float Bf = 0.114f;
-
-    for (i = 0; i < len; i+=4)
-    {
-        int r = destination[RED];
-        int g = destination[GREEN];
-        int b = destination[BLUE];
-        int t = CLAMP(Rf * r + Gf * g + Bf *b);
-
-        destination[RED] = t;
-        destination[GREEN] = t;
-        destination[BLUE] = t;
-    }
-    AndroidBitmap_unlockPixels(env, bitmap);
-}
-
-void JNIFUNCF(ImageFilterBWRed, nativeApplyFilter, jobject bitmap, jint width, jint height)
-{
-    char* destination = 0;
-    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
-    int i;
-    int len = width * height * 4;
-    for (i = 0; i < len; i+=4)
-    {
-        int r = destination[RED];
-        int g = destination[GREEN];
-        int b = destination[BLUE];
-        int t = (g + b) / 2;
-        r = t;
-        g = t;
-        b = t;
-        destination[RED] = r;
-        destination[GREEN] = g;
-        destination[BLUE] = b;
-    }
-    AndroidBitmap_unlockPixels(env, bitmap);
-}
-
-void JNIFUNCF(ImageFilterBWGreen, nativeApplyFilter, jobject bitmap, jint width, jint height)
-{
-    char* destination = 0;
-    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
-    int i;
-    int len = width * height * 4;
-    for (i = 0; i < len; i+=4)
-    {
-        int r = destination[RED];
-        int g = destination[GREEN];
-        int b = destination[BLUE];
-        int t = (r + b) / 2;
-        r = t;
-        g = t;
-        b = t;
-        destination[RED] = r;
-        destination[GREEN] = g;
-        destination[BLUE] = b;
-    }
-    AndroidBitmap_unlockPixels(env, bitmap);
-}
-
-void JNIFUNCF(ImageFilterBWBlue, nativeApplyFilter, jobject bitmap, jint width, jint height)
-{
-    char* destination = 0;
-    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
-    int i;
-    int len = width * height * 4;
-    for (i = 0; i < len; i+=4)
-    {
-        int r = destination[RED];
-        int g = destination[GREEN];
-        int b = destination[BLUE];
-        int t = (r + g) / 2;
-        r = t;
-        g = t;
-        b = t;
-        destination[RED] = r;
-        destination[GREEN] = g;
-        destination[BLUE] = b;
-    }
-    AndroidBitmap_unlockPixels(env, bitmap);
-}
diff --git a/jni/filters/contrast.c b/jni/filters/contrast.c
index 6c1b976..b04e936 100644
--- a/jni/filters/contrast.c
+++ b/jni/filters/contrast.c
@@ -27,6 +27,15 @@
     return  (unsigned char) c;
 }
 
+int clampMax(int c,int max)
+{
+    c &= ~(c >> 31);
+    c -= max;
+    c &= (c >> 31);
+    c += max;
+    return  c;
+}
+
 void JNIFUNCF(ImageFilterContrast, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat bright)
 {
     char* destination = 0;
diff --git a/jni/filters/edge.c b/jni/filters/edge.c
new file mode 100644
index 0000000..9f5d88f
--- /dev/null
+++ b/jni/filters/edge.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <math.h>
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterEdge, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat p)
+{
+    char* destination = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+
+    // using contrast function:
+    // f(v) = exp(-alpha * v^beta)
+    // use beta ~ 1
+
+    float const alpha = 5.0f;
+    float const beta = p;
+    float const c_min = 100.0f;
+    float const c_max = 500.0f;
+
+    // pixels must be 4 bytes
+    char * dst = destination;
+
+    int j, k;
+    char * ptr = destination;
+    int row_stride = 4 * width;
+
+    // set 2 row buffer (avoids bitmap copy)
+    int buf_len = 2 * row_stride;
+    char buf[buf_len];
+    int buf_row_ring = 0;
+
+    // set initial buffer to black
+    memset(buf, 0, buf_len * sizeof(char));
+    for (j = 3; j < buf_len; j+=4) {
+        *(buf + j) = 255;  // set initial alphas
+    }
+
+    // apply sobel filter
+    for (j = 1; j < height - 1; j++) {
+
+        for (k = 1; k < width - 1; k++){
+            int loc = j * row_stride + k * 4;
+
+            float bestx = 0.0f;
+            int l;
+            for (l = 0; l < 3; l++) {
+                float tmp = 0.0f;
+                tmp += *(ptr + (loc - row_stride + 4 + l));
+                tmp += *(ptr + (loc + 4 + l)) * 2.0f;
+                tmp += *(ptr + (loc + row_stride + 4 + l));
+                tmp -= *(ptr + (loc - row_stride - 4 + l));
+                tmp -= *(ptr + (loc - 4 + l)) * 2.0f;
+                tmp -= *(ptr + (loc + row_stride - 4 + l));
+                if (fabs(tmp) > fabs(bestx)) {
+                    bestx = tmp;
+                }
+            }
+
+            float besty = 0.0f;
+            for (l = 0; l < 3; l++) {
+                float tmp = 0.0f;
+                tmp -= *(ptr + (loc - row_stride - 4 + l));
+                tmp -= *(ptr + (loc - row_stride + l)) * 2.0f;
+                tmp -= *(ptr + (loc - row_stride + 4 + l));
+                tmp += *(ptr + (loc + row_stride - 4 + l));
+                tmp += *(ptr + (loc + row_stride + l)) * 2.0f;
+                tmp += *(ptr + (loc + row_stride + 4 + l));
+                if (fabs(tmp) > fabs(besty)) {
+                    besty = tmp;
+                }
+            }
+
+            // compute gradient magnitude
+            float mag = sqrt(bestx * bestx + besty * besty);
+
+            // clamp
+            mag = MIN(MAX(c_min, mag), c_max);
+
+            // scale to [0, 1]
+            mag = (mag - c_min) / (c_max - c_min);
+
+            float ret = 1.0f - exp (- alpha * pow(mag, beta));
+            ret = 255 * ret;
+
+            int off = k * 4;
+            *(buf + buf_row_ring + off) = ret;
+            *(buf + buf_row_ring + off + 1) = ret;
+            *(buf + buf_row_ring + off + 2) = ret;
+            *(buf + buf_row_ring + off + 3) = *(ptr + loc + 3);
+        }
+
+        buf_row_ring += row_stride;
+        buf_row_ring %= buf_len;
+
+        if (j - 1 >= 0) {
+            memcpy((dst + row_stride * (j - 1)), (buf + buf_row_ring), row_stride * sizeof(char));
+        }
+
+    }
+    buf_row_ring += row_stride;
+    buf_row_ring %= buf_len;
+    int second_last_row = row_stride * (height - 2);
+    memcpy((dst + second_last_row), (buf + buf_row_ring), row_stride * sizeof(char));
+
+    // set last row to black
+    int last_row = row_stride * (height - 1);
+    memset((dst + last_row), 0, row_stride * sizeof(char));
+    for (j = 3; j < row_stride; j+=4) {
+        *(dst + last_row + j) = 255;  // set alphas
+    }
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/filters.h b/jni/filters/filters.h
index d518b63..14b69cd 100644
--- a/jni/filters/filters.h
+++ b/jni/filters/filters.h
@@ -44,6 +44,7 @@
 #define CLAMP(c) (MAX(0, MIN(255, c)))
 
 __inline__ unsigned char  clamp(int c);
+__inline__ int clampMax(int c,int max);
 
 extern void rgb2hsv( unsigned char *rgb,int rgbOff,unsigned short *hsv,int hsvOff);
 extern void hsv2rgb(unsigned short *hsv,int hsvOff,unsigned char  *rgb,int rgbOff);
diff --git a/jni/filters/highlight.c b/jni/filters/highlight.c
new file mode 100644
index 0000000..fe9b88f
--- /dev/null
+++ b/jni/filters/highlight.c
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#include <math.h>
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterHighlights, nativeApplyFilter, jobject bitmap,
+              jint width, jint height, jfloatArray luminanceMap){
+    char* destination = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+    unsigned char * rgb = (unsigned char * )destination;
+    int i;
+    int len = width * height * 4;
+    jfloat* lum = (*env)->GetFloatArrayElements(env, luminanceMap,0);
+    unsigned short * hsv = (unsigned short *)malloc(3*sizeof(short));
+
+    for (i = 0; i < len; i+=4)
+    {
+        rgb2hsv(rgb,i,hsv,0);
+        int v = clampMax(hsv[0],4080);
+        hsv[0] = (unsigned short) clampMax(lum[((255*v)/4080)]*4080,4080);
+        hsv2rgb(hsv,0, rgb,i);
+    }
+
+    free(hsv);
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/kmeans.cc b/jni/filters/kmeans.cc
new file mode 100644
index 0000000..97cead7
--- /dev/null
+++ b/jni/filters/kmeans.cc
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "filters.h"
+#include "kmeans.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * For reasonable speeds:
+ * k < 30
+ * small_ds_bitmap width/height < 64 pixels.
+ * large_ds_bitmap width/height < 512 pixels
+ *
+ * bad for high-frequency image noise
+ */
+
+void JNIFUNCF(ImageFilterKMeans, nativeApplyFilter, jobject bitmap, jint width, jint height,
+        jobject large_ds_bitmap, jint lwidth, jint lheight, jobject small_ds_bitmap,
+        jint swidth, jint sheight, jint p, jint seed)
+{
+    char* destination = 0;
+    char* larger_ds_dst = 0;
+    char* smaller_ds_dst = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+    AndroidBitmap_lockPixels(env, large_ds_bitmap, (void**) &larger_ds_dst);
+    AndroidBitmap_lockPixels(env, small_ds_bitmap, (void**) &smaller_ds_dst);
+    unsigned char * dst = (unsigned char *) destination;
+
+    unsigned char * small_ds = (unsigned char *) smaller_ds_dst;
+    unsigned char * large_ds = (unsigned char *) larger_ds_dst;
+
+    // setting for small bitmap
+    int len = swidth * sheight * 4;
+    int dimension = 3;
+    int stride = 4;
+    int iterations = 20;
+    int k = p;
+    unsigned int s = seed;
+    unsigned char finalCentroids[k * stride];
+
+    // get initial picks from small downsampled image
+    runKMeans<unsigned char, int>(k, finalCentroids, small_ds, len, dimension,
+            stride, iterations, s);
+
+
+    len = lwidth * lheight * 4;
+    iterations = 8;
+    unsigned char nextCentroids[k * stride];
+
+    // run kmeans on large downsampled image
+    runKMeansWithPicks<unsigned char, int>(k, nextCentroids, large_ds, len,
+            dimension, stride, iterations, finalCentroids);
+
+    len = width * height * 4;
+
+    // apply to final image
+    applyCentroids<unsigned char, int>(k, nextCentroids, dst, len, dimension, stride);
+
+    AndroidBitmap_unlockPixels(env, small_ds_bitmap);
+    AndroidBitmap_unlockPixels(env, large_ds_bitmap);
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
+#ifdef __cplusplus
+}
+#endif
diff --git a/jni/filters/kmeans.h b/jni/filters/kmeans.h
new file mode 100644
index 0000000..2450605
--- /dev/null
+++ b/jni/filters/kmeans.h
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef KMEANS_H
+#define KMEANS_H
+
+#include <cstdlib>
+#include <math.h>
+
+// Helper functions
+
+template <typename T, typename N>
+inline void sum(T values[], int len, int dimension, int stride, N dst[]) {
+    int x, y;
+    // zero out dst vector
+    for (x = 0; x < dimension; x++) {
+        dst[x] = 0;
+    }
+    for (x = 0; x < len; x+= stride) {
+        for (y = 0; y < dimension; y++) {
+            dst[y] += values[x + y];
+        }
+    }
+}
+
+template <typename T, typename N>
+inline void set(T val1[], N val2[], int dimension) {
+    int x;
+    for (x = 0; x < dimension; x++) {
+        val1[x] = val2[x];
+    }
+}
+
+template <typename T, typename N>
+inline void add(T val[], N dst[], int dimension) {
+    int x;
+    for (x = 0; x < dimension; x++) {
+        dst[x] += val[x];
+    }
+}
+
+template <typename T, typename N>
+inline void divide(T dst[], N divisor, int dimension) {
+   int x;
+   if (divisor == 0) {
+       return;
+   }
+   for (x = 0; x < dimension; x++) {
+       dst[x] /= divisor;
+   }
+}
+
+/**
+ * Calculates euclidean distance.
+ */
+
+template <typename T, typename N>
+inline N euclideanDist(T val1[], T val2[], int dimension) {
+    int x;
+    N sum = 0;
+    for (x = 0; x < dimension; x++) {
+        N diff = (N) val1[x] - (N) val2[x];
+        sum += diff * diff;
+    }
+    return sqrt(sum);
+}
+
+// K-Means
+
+
+/**
+ * Picks k random starting points from the data set.
+ */
+template <typename T>
+void initialPickHeuristicRandom(int k, T values[], int len, int dimension, int stride, T dst[],
+        unsigned int seed) {
+    int x, z, num_vals, cntr;
+    num_vals = len / stride;
+    cntr = 0;
+    srand(seed);
+    unsigned int r_vals[k];
+    unsigned int r;
+
+    for (x = 0; x < k; x++) {
+
+        // ensure randomly chosen value is unique
+        int r_check = 0;
+        while (r_check == 0) {
+            r = (unsigned int) rand() % num_vals;
+            r_check = 1;
+            for (z = 0; z < x; z++) {
+                if (r == r_vals[z]) {
+                    r_check = 0;
+                }
+            }
+        }
+        r_vals[x] = r;
+        r *= stride;
+
+        // set dst to be randomly chosen value
+        set<T,T>(dst + cntr, values + r, dimension);
+        cntr += stride;
+    }
+}
+
+/**
+ * Finds index of closet centroid to a value
+ */
+template <typename T, typename N>
+inline int findClosest(T values[], T oldCenters[], int dimension, int stride, int pop_size) {
+    int best_ind = 0;
+    N best_len = euclideanDist <T, N>(values, oldCenters, dimension);
+    int y;
+    for (y = stride; y < pop_size; y+=stride) {
+        N l = euclideanDist <T, N>(values, oldCenters + y, dimension);
+        if (l < best_len) {
+            best_len = l;
+            best_ind = y;
+        }
+    }
+    return best_ind;
+}
+
+/**
+ * Calculates new centroids by averaging value clusters for old centroids.
+ */
+template <typename T, typename N>
+int calculateNewCentroids(int k, T values[], int len, int dimension, int stride, T oldCenters[],
+        T dst[]) {
+    int x, pop_size;
+    pop_size = k * stride;
+    int popularities[k];
+    N tmp[pop_size];
+
+    //zero popularities
+    memset(popularities, 0, sizeof(int) * k);
+    // zero dst, and tmp
+    for (x = 0; x < pop_size; x++) {
+        tmp[x] = 0;
+    }
+
+    // put summation for each k in tmp
+    for (x = 0; x < len; x+=stride) {
+        int best = findClosest<T, N>(values + x, oldCenters, dimension, stride, pop_size);
+        add<T, N>(values + x, tmp + best, dimension);
+        popularities[best / stride]++;
+
+    }
+
+    int ret = 0;
+    int y;
+    // divide to get centroid and set dst to result
+    for (x = 0; x < pop_size; x+=stride) {
+        divide<N, int>(tmp + x, popularities[x / stride], dimension);
+        for (y = 0; y < dimension; y++) {
+            if ((dst + x)[y] != (T) ((tmp + x)[y])) {
+                ret = 1;
+            }
+        }
+        set(dst + x, tmp + x, dimension);
+    }
+    return ret;
+}
+
+template <typename T, typename N>
+void runKMeansWithPicks(int k, T finalCentroids[], T values[], int len, int dimension, int stride,
+        int iterations, T initialPicks[]){
+        int k_len = k * stride;
+        int x;
+
+        // zero newCenters
+        for (x = 0; x < k_len; x++) {
+            finalCentroids[x] = 0;
+        }
+
+        T * c1 = initialPicks;
+        T * c2 = finalCentroids;
+        T * temp;
+        int ret = 1;
+        for (x = 0; x < iterations; x++) {
+            ret = calculateNewCentroids<T, N>(k, values, len, dimension, stride, c1, c2);
+            temp = c1;
+            c1 = c2;
+            c2 = temp;
+            if (ret == 0) {
+                x = iterations;
+            }
+        }
+        set<T, T>(finalCentroids, c1, dimension);
+}
+
+/**
+ * Runs the k-means algorithm on dataset values with some initial centroids.
+ */
+template <typename T, typename N>
+void runKMeans(int k, T finalCentroids[], T values[], int len, int dimension, int stride,
+        int iterations, unsigned int seed){
+    int k_len = k * stride;
+    T initialPicks [k_len];
+    initialPickHeuristicRandom<T>(k, values, len, dimension, stride, initialPicks, seed);
+
+    runKMeansWithPicks<T, N>(k, finalCentroids, values, len, dimension, stride,
+        iterations, initialPicks);
+}
+
+/**
+ * Sets each value in values to the closest centroid.
+ */
+template <typename T, typename N>
+void applyCentroids(int k, T centroids[], T values[], int len, int dimension, int stride) {
+    int x, pop_size;
+    pop_size = k * stride;
+    for (x = 0; x < len; x+= stride) {
+        int best = findClosest<T, N>(values + x, centroids, dimension, stride, pop_size);
+        set<T, T>(values + x, centroids + best, dimension);
+    }
+}
+
+#endif // KMEANS_H
diff --git a/jni/filters/negative.c b/jni/filters/negative.c
new file mode 100644
index 0000000..735e583
--- /dev/null
+++ b/jni/filters/negative.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "filters.h"
+
+void JNIFUNCF(ImageFilterNegative, nativeApplyFilter, jobject bitmap, jint width, jint height)
+{
+    char* destination = 0;
+    AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
+
+    int tot_len = height * width * 4;
+    int i;
+    char * dst = destination;
+    for (i = 0; i < tot_len; i+=4) {
+        dst[RED] = 255 - dst[RED];
+        dst[GREEN] = 255 - dst[GREEN];
+        dst[BLUE] = 255 - dst[BLUE];
+    }
+    AndroidBitmap_unlockPixels(env, bitmap);
+}
diff --git a/jni/filters/tinyplanet.cc b/jni/filters/tinyplanet.cc
index a40470d..beac086 100644
--- a/jni/filters/tinyplanet.cc
+++ b/jni/filters/tinyplanet.cc
@@ -80,6 +80,7 @@
              ax * ay * p2[4] + axn * ay * p2[0] + 0.5f);
   p++;
   p2++;
+  dest[3] = 0xFF;
 }
 
 // Wrap circular coordinates around the globe
diff --git a/jni/filters/vignette.c b/jni/filters/vignette.c
index 2799ff0..b9ee3ff 100644
--- a/jni/filters/vignette.c
+++ b/jni/filters/vignette.c
@@ -15,52 +15,32 @@
  */
 
 #include "filters.h"
+#include <math.h>
 
 static int* gVignetteMap = 0;
 static int gVignetteWidth = 0;
 static int gVignetteHeight = 0;
 
-__inline__ void createVignetteMap(int w, int h)
-{
-    if (gVignetteMap && (gVignetteWidth != w || gVignetteHeight != h))
-    {
-        free(gVignetteMap);
-        gVignetteMap = 0;
-    }
-    if (gVignetteMap == 0)
-    {
-        gVignetteWidth = w;
-        gVignetteHeight = h;
-
-        int cx = w / 2;
-        int cy = h / 2;
-        int i, j;
-
-        gVignetteMap = malloc(w * h * sizeof(int));
-        float maxDistance = cx * cx * 2.0f;
-        for (i = 0; i < w; i++)
-        {
-            for (j = 0; j < h; j++)
-            {
-                float distance = (cx - i) * (cx - i) + (cy - j) * (cy - j);
-                gVignetteMap[j * w + i] = (int) (distance / maxDistance * 255);
-            }
-        }
-    }
-}
-
-void JNIFUNCF(ImageFilterVignette, nativeApplyFilter, jobject bitmap, jint width, jint height, jfloat strength)
+void JNIFUNCF(ImageFilterVignette, nativeApplyFilter, jobject bitmap, jint width, jint height, jint centerx, jint centery, jfloat radiusx, jfloat radiusy, jfloat strength)
 {
     char* destination = 0;
     AndroidBitmap_lockPixels(env, bitmap, (void**) &destination);
-    createVignetteMap(width, height);
     int i;
     int len = width * height * 4;
     int vignette = 0;
+    float d = centerx;
+    if (radiusx == 0) radiusx = 10;
+    if (radiusy == 0) radiusy = 10;
+    float scalex = 1/radiusx;
+    float scaley = 1/radiusy;
 
     for (i = 0; i < len; i += 4)
     {
-        vignette = (int) (strength * gVignetteMap[i / 4]);
+        int p = i/4;
+        float x = ((p%width)-centerx)*scalex;
+        float y = ((p/width)-centery)*scaley;
+        float dist = sqrt(x*x+y*y)-1;
+        vignette = (int) (strength*256*MAX(dist,0));
         destination[RED] = CLAMP(destination[RED] - vignette);
         destination[GREEN] = CLAMP(destination[GREEN] - vignette);
         destination[BLUE] = CLAMP(destination[BLUE] - vignette);
diff --git a/jni_mosaic/Android.mk b/jni_mosaic/Android.mk
new file mode 100755
index 0000000..9f6f739
--- /dev/null
+++ b/jni_mosaic/Android.mk
@@ -0,0 +1,60 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+        $(LOCAL_PATH)/feature_stab/db_vlvm \
+        $(LOCAL_PATH)/feature_stab/src \
+        $(LOCAL_PATH)/feature_stab/src/dbreg \
+        $(LOCAL_PATH)/feature_mos/src \
+        $(LOCAL_PATH)/feature_mos/src/mosaic
+
+LOCAL_CFLAGS := -O3 -DNDEBUG -fstrict-aliasing
+
+LOCAL_SRC_FILES := \
+        feature_mos_jni.cpp \
+        mosaic_renderer_jni.cpp \
+        feature_mos/src/mosaic/trsMatrix.cpp \
+        feature_mos/src/mosaic/AlignFeatures.cpp \
+        feature_mos/src/mosaic/Blend.cpp \
+        feature_mos/src/mosaic/Delaunay.cpp \
+        feature_mos/src/mosaic/ImageUtils.cpp \
+        feature_mos/src/mosaic/Mosaic.cpp \
+        feature_mos/src/mosaic/Pyramid.cpp \
+        feature_mos/src/mosaic_renderer/Renderer.cpp \
+        feature_mos/src/mosaic_renderer/WarpRenderer.cpp \
+        feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp \
+        feature_mos/src/mosaic_renderer/YVURenderer.cpp \
+        feature_mos/src/mosaic_renderer/FrameBuffer.cpp \
+        feature_stab/db_vlvm/db_feature_detection.cpp \
+        feature_stab/db_vlvm/db_feature_matching.cpp \
+        feature_stab/db_vlvm/db_framestitching.cpp \
+        feature_stab/db_vlvm/db_image_homography.cpp \
+        feature_stab/db_vlvm/db_rob_image_homography.cpp \
+        feature_stab/db_vlvm/db_utilities.cpp \
+        feature_stab/db_vlvm/db_utilities_camera.cpp \
+        feature_stab/db_vlvm/db_utilities_indexing.cpp \
+        feature_stab/db_vlvm/db_utilities_linalg.cpp \
+        feature_stab/db_vlvm/db_utilities_poly.cpp \
+        feature_stab/src/dbreg/dbreg.cpp \
+        feature_stab/src/dbreg/dbstabsmooth.cpp \
+        feature_stab/src/dbreg/vp_motionmodel.c
+
+ifeq ($(TARGET_ARCH), arm)
+        LOCAL_SDK_VERSION := 9
+endif
+
+ifeq ($(TARGET_ARCH), x86)
+        LOCAL_SDK_VERSION := 9
+endif
+
+ifeq ($(TARGET_ARCH), mips)
+        LOCAL_SDK_VERSION := 9
+endif
+
+LOCAL_LDFLAGS := -llog -lGLESv2
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE    := libjni_mosaic
+include $(BUILD_SHARED_LIBRARY)
diff --git a/jni_mosaic/NOTICE b/jni_mosaic/NOTICE
new file mode 100644
index 0000000..7317ae2
--- /dev/null
+++ b/jni_mosaic/NOTICE
@@ -0,0 +1,190 @@
+
+   Copyright (c) 2005-2011, 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.
+
+   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.
+
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
diff --git a/jni_mosaic/feature_mos/doc/Readme.txt b/jni_mosaic/feature_mos/doc/Readme.txt
new file mode 100644
index 0000000..83ce165
--- /dev/null
+++ b/jni_mosaic/feature_mos/doc/Readme.txt
@@ -0,0 +1,3 @@
+To generate the html docs, execute
+doxygen feature_mos_API_doxyfile
+
diff --git a/jni_mosaic/feature_mos/doc/feature_mos_API_doxyfile b/jni_mosaic/feature_mos/doc/feature_mos_API_doxyfile
new file mode 100755
index 0000000..dca8c8c
--- /dev/null
+++ b/jni_mosaic/feature_mos/doc/feature_mos_API_doxyfile
@@ -0,0 +1,1557 @@
+# Doxyfile 1.6.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           =
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = .
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set
+# FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.  This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = ../src/mosaic/Mosaic.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.d \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.cs \
+                         *.php \
+                         *.php3 \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.f90 \
+                         *.f \
+                         *.vhd \
+                         *.vhdl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.  Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.  The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# When the SEARCHENGINE tag is enable doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
+# there is already a search function so this one should typically
+# be disabled.
+
+SEARCHENGINE           = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.  This is useful
+# if you want to understand what is going on.  On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#   TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.cpp b/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.cpp
new file mode 100644
index 0000000..aeabf8f
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// AlignFeatures.cpp
+// S.O. # :
+// Author(s): zkira, mbansal, bsouthall, narodits
+// $Id: AlignFeatures.cpp,v 1.20 2011/06/17 13:35:47 mbansal Exp $
+
+#include <stdio.h>
+#include <string.h>
+
+#include "trsMatrix.h"
+#include "MatrixUtils.h"
+#include "AlignFeatures.h"
+#include "Log.h"
+
+#define LOG_TAG "AlignFeatures"
+
+Align::Align()
+{
+  width = height = 0;
+  frame_number = 0;
+  num_frames_captured = 0;
+  reference_frame_index = 0;
+  db_Identity3x3(Hcurr);
+  db_Identity3x3(Hprev);
+}
+
+Align::~Align()
+{
+  // Free gray-scale image
+  if (imageGray != ImageUtils::IMAGE_TYPE_NOIMAGE)
+    ImageUtils::freeImage(imageGray);
+}
+
+char* Align::getRegProfileString()
+{
+  return reg.profile_string;
+}
+
+int Align::initialize(int width, int height, bool _quarter_res, float _thresh_still)
+{
+  int    nr_corners = DEFAULT_NR_CORNERS;
+  double max_disparity = DEFAULT_MAX_DISPARITY;
+  int    motion_model_type = DEFAULT_MOTION_MODEL;
+  int nrsamples = DB_DEFAULT_NR_SAMPLES;
+  double scale = DB_POINT_STANDARDDEV;
+  int chunk_size = DB_DEFAULT_CHUNK_SIZE;
+  int nrhorz = width/48;  // Empirically determined number of horizontal
+  int nrvert = height/60; // and vertical buckets for harris corner detection.
+  bool linear_polish = false;
+  unsigned int reference_update_period = DEFAULT_REFERENCE_UPDATE_PERIOD;
+
+  const bool DEFAULT_USE_SMALLER_MATCHING_WINDOW = false;
+  bool   use_smaller_matching_window = DEFAULT_USE_SMALLER_MATCHING_WINDOW;
+
+  quarter_res = _quarter_res;
+  thresh_still = _thresh_still;
+
+  frame_number = 0;
+  num_frames_captured = 0;
+  reference_frame_index = 0;
+  db_Identity3x3(Hcurr);
+  db_Identity3x3(Hprev);
+
+  if (!reg.Initialized())
+  {
+    reg.Init(width, height, motion_model_type, 20, linear_polish, quarter_res,
+            scale, reference_update_period, false, 0, nrsamples, chunk_size,
+            nr_corners, max_disparity, use_smaller_matching_window,
+            nrhorz, nrvert);
+  }
+  this->width = width;
+  this->height = height;
+
+  imageGray = ImageUtils::allocateImage(width, height, 1);
+
+  if (reg.Initialized())
+    return ALIGN_RET_OK;
+  else
+    return ALIGN_RET_ERROR;
+}
+
+int Align::addFrameRGB(ImageType imageRGB)
+{
+  ImageUtils::rgb2gray(imageGray, imageRGB, width, height);
+  return addFrame(imageGray);
+}
+
+int Align::addFrame(ImageType imageGray_)
+{
+  int ret_code = ALIGN_RET_OK;
+
+ // Obtain a vector of pointers to rows in image and pass in to dbreg
+  ImageType *m_rows = ImageUtils::imageTypeToRowPointers(imageGray_, width, height);
+
+  if (frame_number == 0)
+  {
+      reg.AddFrame(m_rows, Hcurr, true);    // Force this to be a reference frame
+      int num_corner_ref = reg.GetNrRefCorners();
+
+      if (num_corner_ref < MIN_NR_REF_CORNERS)
+      {
+          return ALIGN_RET_LOW_TEXTURE;
+      }
+  }
+  else
+  {
+      reg.AddFrame(m_rows, Hcurr, false);
+  }
+
+  // Average translation per frame =
+  //    [Translation from Frame0 to Frame(n-1)] / [(n-1)]
+  average_tx_per_frame = (num_frames_captured < 2) ? 0.0 :
+        Hprev[2] / (num_frames_captured - 1);
+
+  // Increment the captured frame counter if we already have a reference frame
+  num_frames_captured++;
+
+  if (frame_number != 0)
+  {
+    int num_inliers = reg.GetNrInliers();
+
+    if(num_inliers < MIN_NR_INLIERS)
+    {
+        ret_code = ALIGN_RET_FEW_INLIERS;
+
+        Hcurr[0] = 1.0;
+        Hcurr[1] = 0.0;
+        // Set this as the average per frame translation taking into acccount
+        // the separation of the current frame from the reference frame...
+        Hcurr[2] = -average_tx_per_frame *
+                (num_frames_captured - reference_frame_index);
+        Hcurr[3] = 0.0;
+        Hcurr[4] = 1.0;
+        Hcurr[5] = 0.0;
+        Hcurr[6] = 0.0;
+        Hcurr[7] = 0.0;
+        Hcurr[8] = 1.0;
+    }
+
+    if(fabs(Hcurr[2])<thresh_still && fabs(Hcurr[5])<thresh_still)  // Still camera
+    {
+        return ALIGN_RET_ERROR;
+    }
+
+    // compute the homography:
+    double Hinv33[3][3];
+    double Hprev33[3][3];
+    double Hcurr33[3][3];
+
+    // Invert and multiple with previous transformation
+    Matrix33::convert9to33(Hcurr33, Hcurr);
+    Matrix33::convert9to33(Hprev33, Hprev);
+    normProjMat33d(Hcurr33);
+
+    inv33d(Hcurr33, Hinv33);
+
+    mult33d(Hcurr33, Hprev33, Hinv33);
+    normProjMat33d(Hcurr33);
+    Matrix9::convert33to9(Hprev, Hcurr33);
+    // Since we have already factored the current transformation
+    // into Hprev, we can reset the Hcurr to identity
+    db_Identity3x3(Hcurr);
+
+    // Update the reference frame to be the current frame
+    reg.UpdateReference(m_rows,quarter_res,false);
+
+    // Update the reference frame index
+    reference_frame_index = num_frames_captured;
+  }
+
+  frame_number++;
+
+  return ret_code;
+}
+
+// Get current transformation
+int Align::getLastTRS(double trs[3][3])
+{
+  if (frame_number < 1)
+  {
+    trs[0][0] = 1.0;
+    trs[0][1] = 0.0;
+    trs[0][2] = 0.0;
+    trs[1][0] = 0.0;
+    trs[1][1] = 1.0;
+    trs[1][2] = 0.0;
+    trs[2][0] = 0.0;
+    trs[2][1] = 0.0;
+    trs[2][2] = 1.0;
+    return ALIGN_RET_ERROR;
+  }
+
+  // Note that the logic here handles the case, where a frame is not used for
+  // mosaicing but is captured and used in the preview-rendering.
+  // For these frames, we don't set Hcurr to identity in AddFrame() and the
+  // logic here appends their transformation to Hprev to render them with the
+  // correct transformation. For the frames we do use for mosaicing, we already
+  // append their Hcurr to Hprev in AddFrame() and then set Hcurr to identity.
+
+  double Hinv33[3][3];
+  double Hprev33[3][3];
+  double Hcurr33[3][3];
+
+  Matrix33::convert9to33(Hcurr33, Hcurr);
+  normProjMat33d(Hcurr33);
+  inv33d(Hcurr33, Hinv33);
+
+  Matrix33::convert9to33(Hprev33, Hprev);
+
+  mult33d(trs, Hprev33, Hinv33);
+  normProjMat33d(trs);
+
+  return ALIGN_RET_OK;
+}
+
diff --git a/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.h b/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.h
new file mode 100644
index 0000000..19f3905
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/AlignFeatures.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Align.h
+// S.O. # :
+// Author(s): zkira
+// $Id: AlignFeatures.h,v 1.13 2011/06/17 13:35:47 mbansal Exp $
+
+#ifndef ALIGN_H
+#define ALIGN_H
+
+#include "dbreg/dbreg.h"
+#include <db_utilities_camera.h>
+
+#include "ImageUtils.h"
+#include "MatrixUtils.h"
+
+class Align {
+
+public:
+  // Types of alignment possible
+  static const int ALIGN_TYPE_PAN    = 1;
+
+  // Return codes
+  static const int ALIGN_RET_LOW_TEXTURE  = -2;
+  static const int ALIGN_RET_ERROR        = -1;
+  static const int ALIGN_RET_OK           = 0;
+  static const int ALIGN_RET_FEW_INLIERS  = 1;
+
+  ///// Settings for feature-based alignment
+  // Number of features to use from corner detection
+  static const int DEFAULT_NR_CORNERS=750;
+  static const double DEFAULT_MAX_DISPARITY=0.1;//0.4;
+  // Type of homography to model
+  static const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_R_T;
+// static const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_PROJECTIVE;
+//  static const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_AFFINE;
+  static const unsigned int DEFAULT_REFERENCE_UPDATE_PERIOD=1500; //  Manual reference frame update so set this to a large number
+
+  static const int MIN_NR_REF_CORNERS = 25;
+  static const int MIN_NR_INLIERS = 10;
+
+  Align();
+  ~Align();
+
+  // Initialization of structures, etc.
+  int initialize(int width, int height, bool quarter_res, float thresh_still);
+
+  // Add a frame.  Note: The alignment computation is performed
+  // in this function
+  int addFrameRGB(ImageType image);
+  int addFrame(ImageType image);
+
+  // Obtain the TRS matrix from the last two frames
+  int getLastTRS(double trs[3][3]);
+  char* getRegProfileString();
+
+protected:
+
+  db_FrameToReferenceRegistration reg;
+
+  int frame_number;
+
+  double Hcurr[9];   // Homography from the alignment reference to the frame-t
+  double Hprev[9];   // Homography from frame-0 to the frame-(t-1)
+
+  int reference_frame_index; // Index of the reference frame from all captured frames
+  int num_frames_captured; // Total number of frames captured (different from frame_number)
+  double average_tx_per_frame; // Average pixel translation per captured frame
+
+  int width,height;
+
+  bool quarter_res;     // Whether to process at quarter resolution
+  float thresh_still;   // Translation threshold in pixels to detect still camera
+  ImageType imageGray;
+};
+
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Blend.cpp b/jni_mosaic/feature_mos/src/mosaic/Blend.cpp
new file mode 100644
index 0000000..ef983ff
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Blend.cpp
@@ -0,0 +1,1410 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Blend.cpp
+// $Id: Blend.cpp,v 1.22 2011/06/24 04:22:14 mbansal Exp $
+
+#include <string.h>
+
+#include "Interp.h"
+#include "Blend.h"
+
+#include "Geometry.h"
+#include "trsMatrix.h"
+
+#include "Log.h"
+#define LOG_TAG "BLEND"
+
+Blend::Blend()
+{
+  m_wb.blendingType = BLEND_TYPE_NONE;
+}
+
+Blend::~Blend()
+{
+    if (m_pFrameVPyr) free(m_pFrameVPyr);
+    if (m_pFrameUPyr) free(m_pFrameUPyr);
+    if (m_pFrameYPyr) free(m_pFrameYPyr);
+}
+
+int Blend::initialize(int blendingType, int stripType, int frame_width, int frame_height)
+{
+    this->width = frame_width;
+    this->height = frame_height;
+    this->m_wb.blendingType = blendingType;
+    this->m_wb.stripType = stripType;
+
+    m_wb.blendRange = m_wb.blendRangeUV = BLEND_RANGE_DEFAULT;
+    m_wb.nlevs = m_wb.blendRange;
+    m_wb.nlevsC = m_wb.blendRangeUV;
+
+    if (m_wb.nlevs <= 0) m_wb.nlevs = 1; // Need levels for YUV processing
+    if (m_wb.nlevsC > m_wb.nlevs) m_wb.nlevsC = m_wb.nlevs;
+
+    m_wb.roundoffOverlap = 1.5;
+
+    m_pFrameYPyr = NULL;
+    m_pFrameUPyr = NULL;
+    m_pFrameVPyr = NULL;
+
+    m_pFrameYPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevs, (unsigned short) width, (unsigned short) height, BORDER);
+    m_pFrameUPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevsC, (unsigned short) (width), (unsigned short) (height), BORDER);
+    m_pFrameVPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevsC, (unsigned short) (width), (unsigned short) (height), BORDER);
+
+    if (!m_pFrameYPyr || !m_pFrameUPyr || !m_pFrameVPyr)
+    {
+        LOGE("Error: Could not allocate pyramids for blending");
+        return BLEND_RET_ERROR_MEMORY;
+    }
+
+    return BLEND_RET_OK;
+}
+
+inline double max(double a, double b) { return a > b ? a : b; }
+inline double min(double a, double b) { return a < b ? a : b; }
+
+void Blend::AlignToMiddleFrame(MosaicFrame **frames, int frames_size)
+{
+    // Unwarp this frame and Warp the others to match
+    MosaicFrame *mb = NULL;
+    MosaicFrame *ref = frames[int(frames_size/2)];    // Middle frame
+
+    double invtrs[3][3];
+    inv33d(ref->trs, invtrs);
+
+    for(int mfit = 0; mfit < frames_size; mfit++)
+    {
+        mb = frames[mfit];
+        double temp[3][3];
+        mult33d(temp, invtrs, mb->trs);
+        memcpy(mb->trs, temp, sizeof(temp));
+        normProjMat33d(mb->trs);
+    }
+}
+
+int Blend::runBlend(MosaicFrame **oframes, MosaicFrame **rframes,
+        int frames_size,
+        ImageType &imageMosaicYVU, int &mosaicWidth, int &mosaicHeight,
+        float &progress, bool &cancelComputation)
+{
+    int ret;
+    int numCenters;
+
+    MosaicFrame **frames;
+
+    // For THIN strip mode, accept all frames for blending
+    if (m_wb.stripType == STRIP_TYPE_THIN)
+    {
+        frames = oframes;
+    }
+    else // For WIDE strip mode, first select the relevant frames to blend.
+    {
+        SelectRelevantFrames(oframes, frames_size, rframes, frames_size);
+        frames = rframes;
+    }
+
+    ComputeBlendParameters(frames, frames_size, true);
+    numCenters = frames_size;
+
+    if (numCenters == 0)
+    {
+        LOGE("Error: No frames to blend");
+        return BLEND_RET_ERROR;
+    }
+
+    if (!(m_AllSites = m_Triangulator.allocMemory(numCenters)))
+    {
+        return BLEND_RET_ERROR_MEMORY;
+    }
+
+    // Bounding rectangle (real numbers) of the final mosaic computed by projecting
+    // each input frame into the mosaic coordinate system.
+    BlendRect global_rect;
+
+    global_rect.lft = global_rect.bot = 2e30; // min values
+    global_rect.rgt = global_rect.top = -2e30; // max values
+    MosaicFrame *mb = NULL;
+    double halfwidth = width / 2.0;
+    double halfheight = height / 2.0;
+
+    double z, x0, y0, x1, y1, x2, y2, x3, y3;
+
+    // Corners of the left-most and right-most frames respectively in the
+    // mosaic coordinate system.
+    double xLeftCorners[2] = {2e30, 2e30};
+    double xRightCorners[2] = {-2e30, -2e30};
+
+    // Corners of the top-most and bottom-most frames respectively in the
+    // mosaic coordinate system.
+    double yTopCorners[2] = {2e30, 2e30};
+    double yBottomCorners[2] = {-2e30, -2e30};
+
+
+    // Determine the extents of the final mosaic
+    CSite *csite = m_AllSites ;
+    for(int mfit = 0; mfit < frames_size; mfit++)
+    {
+        mb = frames[mfit];
+
+        // Compute clipping for this frame's rect
+        FrameToMosaicRect(mb->width, mb->height, mb->trs, mb->brect);
+        // Clip global rect using this frame's rect
+        ClipRect(mb->brect, global_rect);
+
+        // Calculate the corner points
+        FrameToMosaic(mb->trs, 0.0,             0.0,            x0, y0);
+        FrameToMosaic(mb->trs, 0.0,             mb->height-1.0, x1, y1);
+        FrameToMosaic(mb->trs, mb->width-1.0,   mb->height-1.0, x2, y2);
+        FrameToMosaic(mb->trs, mb->width-1.0,   0.0,            x3, y3);
+
+        if(x0 < xLeftCorners[0] || x1 < xLeftCorners[1])    // If either of the left corners is lower
+        {
+            xLeftCorners[0] = x0;
+            xLeftCorners[1] = x1;
+        }
+
+        if(x3 > xRightCorners[0] || x2 > xRightCorners[1])    // If either of the right corners is higher
+        {
+            xRightCorners[0] = x3;
+            xRightCorners[1] = x2;
+        }
+
+        if(y0 < yTopCorners[0] || y3 < yTopCorners[1])    // If either of the top corners is lower
+        {
+            yTopCorners[0] = y0;
+            yTopCorners[1] = y3;
+        }
+
+        if(y1 > yBottomCorners[0] || y2 > yBottomCorners[1])    // If either of the bottom corners is higher
+        {
+            yBottomCorners[0] = y1;
+            yBottomCorners[1] = y2;
+        }
+
+
+        // Compute the centroid of the warped region
+        FindQuadCentroid(x0, y0, x1, y1, x2, y2, x3, y3, csite->getVCenter().x, csite->getVCenter().y);
+
+        csite->setMb(mb);
+        csite++;
+    }
+
+    // Get origin and sizes
+
+    // Bounding rectangle (int numbers) of the final mosaic computed by projecting
+    // each input frame into the mosaic coordinate system.
+    MosaicRect fullRect;
+
+    fullRect.left = (int) floor(global_rect.lft); // min-x
+    fullRect.top = (int) floor(global_rect.bot);  // min-y
+    fullRect.right = (int) ceil(global_rect.rgt); // max-x
+    fullRect.bottom = (int) ceil(global_rect.top);// max-y
+    Mwidth = (unsigned short) (fullRect.right - fullRect.left + 1);
+    Mheight = (unsigned short) (fullRect.bottom - fullRect.top + 1);
+
+    int xLeftMost, xRightMost;
+    int yTopMost, yBottomMost;
+
+    // Rounding up, so that we don't include the gray border.
+    xLeftMost = max(0, max(xLeftCorners[0], xLeftCorners[1]) - fullRect.left + 1);
+    xRightMost = min(Mwidth - 1, min(xRightCorners[0], xRightCorners[1]) - fullRect.left - 1);
+
+    yTopMost = max(0, max(yTopCorners[0], yTopCorners[1]) - fullRect.top + 1);
+    yBottomMost = min(Mheight - 1, min(yBottomCorners[0], yBottomCorners[1]) - fullRect.top - 1);
+
+    if (xRightMost <= xLeftMost || yBottomMost <= yTopMost)
+    {
+        LOGE("RunBlend: aborting -consistency check failed,"
+             "(xLeftMost, xRightMost, yTopMost, yBottomMost): (%d, %d, %d, %d)",
+             xLeftMost, xRightMost, yTopMost, yBottomMost);
+        return BLEND_RET_ERROR;
+    }
+
+    // Make sure image width is multiple of 4
+    Mwidth = (unsigned short) ((Mwidth + 3) & ~3);
+    Mheight = (unsigned short) ((Mheight + 3) & ~3);    // Round up.
+
+    ret = MosaicSizeCheck(LIMIT_SIZE_MULTIPLIER, LIMIT_HEIGHT_MULTIPLIER);
+    if (ret != BLEND_RET_OK)
+    {
+       LOGE("RunBlend: aborting - mosaic size check failed, "
+            "(frame_width, frame_height) vs (mosaic_width, mosaic_height): "
+            "(%d, %d) vs (%d, %d)", width, height, Mwidth, Mheight);
+       return ret;
+    }
+
+    LOGI("Allocate mosaic image for blending - size: %d x %d", Mwidth, Mheight);
+    YUVinfo *imgMos = YUVinfo::allocateImage(Mwidth, Mheight);
+    if (imgMos == NULL)
+    {
+        LOGE("RunBlend: aborting - couldn't alloc %d x %d mosaic image", Mwidth, Mheight);
+        return BLEND_RET_ERROR_MEMORY;
+    }
+
+    // Set the Y image to 255 so we can distinguish when frame idx are written to it
+    memset(imgMos->Y.ptr[0], 255, (imgMos->Y.width * imgMos->Y.height));
+    // Set the v and u images to black
+    memset(imgMos->V.ptr[0], 128, (imgMos->V.width * imgMos->V.height) << 1);
+
+    // Do the triangulation.  It returns a sorted list of edges
+    SEdgeVector *edge;
+    int n = m_Triangulator.triangulate(&edge, numCenters, width, height);
+    m_Triangulator.linkNeighbors(edge, n, numCenters);
+
+    // Bounding rectangle that determines the positioning of the rectangle that is
+    // cropped out of the computed mosaic to get rid of the gray borders.
+    MosaicRect cropping_rect;
+
+    if (m_wb.horizontal)
+    {
+        cropping_rect.left = xLeftMost;
+        cropping_rect.right = xRightMost;
+    }
+    else
+    {
+        cropping_rect.top = yTopMost;
+        cropping_rect.bottom = yBottomMost;
+    }
+
+    // Do merging and blending :
+    ret = DoMergeAndBlend(frames, numCenters, width, height, *imgMos, fullRect,
+            cropping_rect, progress, cancelComputation);
+
+    if (m_wb.blendingType == BLEND_TYPE_HORZ)
+        CropFinalMosaic(*imgMos, cropping_rect);
+
+
+    m_Triangulator.freeMemory();    // note: can be called even if delaunay_alloc() wasn't successful
+
+    imageMosaicYVU = imgMos->Y.ptr[0];
+
+
+    if (m_wb.blendingType == BLEND_TYPE_HORZ)
+    {
+        mosaicWidth = cropping_rect.right - cropping_rect.left + 1;
+        mosaicHeight = cropping_rect.bottom - cropping_rect.top + 1;
+    }
+    else
+    {
+        mosaicWidth = Mwidth;
+        mosaicHeight = Mheight;
+    }
+
+    return ret;
+}
+
+int Blend::MosaicSizeCheck(float sizeMultiplier, float heightMultiplier) {
+   if (Mwidth < width || Mheight < height) {
+        return BLEND_RET_ERROR;
+    }
+
+   if ((Mwidth * Mheight) > (width * height * sizeMultiplier)) {
+         return BLEND_RET_ERROR;
+   }
+
+   // We won't do blending for the cases where users swing the device too much
+   // in the secondary direction. We use a short side to determine the
+   // secondary direction because users may hold the device in landsape
+   // or portrait.
+   int shortSide = min(Mwidth, Mheight);
+   if (shortSide > height * heightMultiplier) {
+       return BLEND_RET_ERROR;
+   }
+
+   return BLEND_RET_OK;
+}
+
+int Blend::FillFramePyramid(MosaicFrame *mb)
+{
+    ImageType mbY, mbU, mbV;
+    // Lay this image, centered into the temporary buffer
+    mbY = mb->image;
+    mbU = mb->getU();
+    mbV = mb->getV();
+
+    int h, w;
+
+    for(h=0; h<height; h++)
+    {
+        ImageTypeShort yptr = m_pFrameYPyr->ptr[h];
+        ImageTypeShort uptr = m_pFrameUPyr->ptr[h];
+        ImageTypeShort vptr = m_pFrameVPyr->ptr[h];
+
+        for(w=0; w<width; w++)
+        {
+            yptr[w] = (short) ((*(mbY++)) << 3);
+            uptr[w] = (short) ((*(mbU++)) << 3);
+            vptr[w] = (short) ((*(mbV++)) << 3);
+        }
+    }
+
+    // Spread the image through the border
+    PyramidShort::BorderSpread(m_pFrameYPyr, BORDER, BORDER, BORDER, BORDER);
+    PyramidShort::BorderSpread(m_pFrameUPyr, BORDER, BORDER, BORDER, BORDER);
+    PyramidShort::BorderSpread(m_pFrameVPyr, BORDER, BORDER, BORDER, BORDER);
+
+    // Generate Laplacian pyramids
+    if (!PyramidShort::BorderReduce(m_pFrameYPyr, m_wb.nlevs) || !PyramidShort::BorderExpand(m_pFrameYPyr, m_wb.nlevs, -1) ||
+            !PyramidShort::BorderReduce(m_pFrameUPyr, m_wb.nlevsC) || !PyramidShort::BorderExpand(m_pFrameUPyr, m_wb.nlevsC, -1) ||
+            !PyramidShort::BorderReduce(m_pFrameVPyr, m_wb.nlevsC) || !PyramidShort::BorderExpand(m_pFrameVPyr, m_wb.nlevsC, -1))
+    {
+        LOGE("Error: Could not generate Laplacian pyramids");
+        return BLEND_RET_ERROR;
+    }
+    else
+    {
+        return BLEND_RET_OK;
+    }
+}
+
+int Blend::DoMergeAndBlend(MosaicFrame **frames, int nsite,
+             int width, int height, YUVinfo &imgMos, MosaicRect &rect,
+             MosaicRect &cropping_rect, float &progress, bool &cancelComputation)
+{
+    m_pMosaicYPyr = NULL;
+    m_pMosaicUPyr = NULL;
+    m_pMosaicVPyr = NULL;
+
+    m_pMosaicYPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevs,(unsigned short)rect.Width(),(unsigned short)rect.Height(),BORDER);
+    m_pMosaicUPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevsC,(unsigned short)rect.Width(),(unsigned short)rect.Height(),BORDER);
+    m_pMosaicVPyr = PyramidShort::allocatePyramidPacked(m_wb.nlevsC,(unsigned short)rect.Width(),(unsigned short)rect.Height(),BORDER);
+    if (!m_pMosaicYPyr || !m_pMosaicUPyr || !m_pMosaicVPyr)
+    {
+      LOGE("Error: Could not allocate pyramids for blending");
+      return BLEND_RET_ERROR_MEMORY;
+    }
+
+    MosaicFrame *mb;
+
+    CSite *esite = m_AllSites + nsite;
+    int site_idx;
+
+    // First go through each frame and for each mosaic pixel determine which frame it should come from
+    site_idx = 0;
+    for(CSite *csite = m_AllSites; csite < esite; csite++)
+    {
+        if(cancelComputation)
+        {
+            if (m_pMosaicVPyr) free(m_pMosaicVPyr);
+            if (m_pMosaicUPyr) free(m_pMosaicUPyr);
+            if (m_pMosaicYPyr) free(m_pMosaicYPyr);
+            return BLEND_RET_CANCELLED;
+        }
+
+        mb = csite->getMb();
+
+        mb->vcrect = mb->brect;
+        ClipBlendRect(csite, mb->vcrect);
+
+        ComputeMask(csite, mb->vcrect, mb->brect, rect, imgMos, site_idx);
+
+        site_idx++;
+    }
+
+    ////////// imgMos.Y, imgMos.V, imgMos.U are used as follows //////////////
+    ////////////////////// THIN STRIP MODE ///////////////////////////////////
+
+    // imgMos.Y is used to store the index of the image from which each pixel
+    // in the output mosaic can be read out for the thin-strip mode. Thus,
+    // there is no special handling for pixels around the seam. Also, imgMos.Y
+    // is set to 255 wherever we can't get its value from any input image e.g.
+    // in the gray border areas. imgMos.V and imgMos.U are set to 128 for the
+    // thin-strip mode.
+
+    ////////////////////// WIDE STRIP MODE ///////////////////////////////////
+
+    // imgMos.Y is used the same way as the thin-strip mode.
+    // imgMos.V is used to store the index of the neighboring image which
+    // should contribute to the color of an output pixel in a band around
+    // the seam. Thus, in this band, we will crossfade between the color values
+    // from the image index imgMos.Y and image index imgMos.V. imgMos.U is
+    // used to store the weight (multiplied by 100) that each image will
+    // contribute to the blending process. Thus, we start at 99% contribution
+    // from the first image, then go to 50% contribution from each image at
+    // the seam. Then, the contribution from the second image goes up to 99%.
+
+    // For WIDE mode, set the pixel masks to guide the blender to cross-fade
+    // between the images on either side of each seam:
+    if (m_wb.stripType == STRIP_TYPE_WIDE)
+    {
+        if(m_wb.horizontal)
+        {
+            // Set the number of pixels around the seam to cross-fade between
+            // the two component images,
+            int tw = STRIP_CROSS_FADE_WIDTH_PXLS;
+
+            // Proceed with the image index calculation for cross-fading
+            // only if the cross-fading width is larger than 0
+            if (tw > 0)
+            {
+                for(int y = 0; y < imgMos.Y.height; y++)
+                {
+                    // Since we compare two adjecant pixels to determine
+                    // whether there is a seam, the termination condition of x
+                    // is set to imgMos.Y.width - tw, so that x+1 below
+                    // won't exceed the imgMos' boundary.
+                    for(int x = tw; x < imgMos.Y.width - tw; )
+                    {
+                        // Determine where the seam is...
+                        if (imgMos.Y.ptr[y][x] != imgMos.Y.ptr[y][x+1] &&
+                                imgMos.Y.ptr[y][x] != 255 &&
+                                imgMos.Y.ptr[y][x+1] != 255)
+                        {
+                            // Find the image indices on both sides of the seam
+                            unsigned char idx1 = imgMos.Y.ptr[y][x];
+                            unsigned char idx2 = imgMos.Y.ptr[y][x+1];
+
+                            for (int o = tw; o >= 0; o--)
+                            {
+                                // Set the image index to use for cross-fading
+                                imgMos.V.ptr[y][x - o] = idx2;
+                                // Set the intensity weights to use for cross-fading
+                                imgMos.U.ptr[y][x - o] = 50 + (99 - 50) * o / tw;
+                            }
+
+                            for (int o = 1; o <= tw; o++)
+                            {
+                                // Set the image index to use for cross-fading
+                                imgMos.V.ptr[y][x + o] = idx1;
+                                // Set the intensity weights to use for cross-fading
+                                imgMos.U.ptr[y][x + o] = imgMos.U.ptr[y][x - o];
+                            }
+
+                            x += (tw + 1);
+                        }
+                        else
+                        {
+                            x++;
+                        }
+                    }
+                }
+            }
+        }
+        else
+        {
+            // Set the number of pixels around the seam to cross-fade between
+            // the two component images,
+            int tw = STRIP_CROSS_FADE_WIDTH_PXLS;
+
+            // Proceed with the image index calculation for cross-fading
+            // only if the cross-fading width is larger than 0
+            if (tw > 0)
+            {
+                for(int x = 0; x < imgMos.Y.width; x++)
+                {
+                    // Since we compare two adjecant pixels to determine
+                    // whether there is a seam, the termination condition of y
+                    // is set to imgMos.Y.height - tw, so that y+1 below
+                    // won't exceed the imgMos' boundary.
+                    for(int y = tw; y < imgMos.Y.height - tw; )
+                    {
+                        // Determine where the seam is...
+                        if (imgMos.Y.ptr[y][x] != imgMos.Y.ptr[y+1][x] &&
+                                imgMos.Y.ptr[y][x] != 255 &&
+                                imgMos.Y.ptr[y+1][x] != 255)
+                        {
+                            // Find the image indices on both sides of the seam
+                            unsigned char idx1 = imgMos.Y.ptr[y][x];
+                            unsigned char idx2 = imgMos.Y.ptr[y+1][x];
+
+                            for (int o = tw; o >= 0; o--)
+                            {
+                                // Set the image index to use for cross-fading
+                                imgMos.V.ptr[y - o][x] = idx2;
+                                // Set the intensity weights to use for cross-fading
+                                imgMos.U.ptr[y - o][x] = 50 + (99 - 50) * o / tw;
+                            }
+
+                            for (int o = 1; o <= tw; o++)
+                            {
+                                // Set the image index to use for cross-fading
+                                imgMos.V.ptr[y + o][x] = idx1;
+                                // Set the intensity weights to use for cross-fading
+                                imgMos.U.ptr[y + o][x] = imgMos.U.ptr[y - o][x];
+                            }
+
+                            y += (tw + 1);
+                        }
+                        else
+                        {
+                            y++;
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+    // Now perform the actual blending using the frame assignment determined above
+    site_idx = 0;
+    for(CSite *csite = m_AllSites; csite < esite; csite++)
+    {
+        if(cancelComputation)
+        {
+            if (m_pMosaicVPyr) free(m_pMosaicVPyr);
+            if (m_pMosaicUPyr) free(m_pMosaicUPyr);
+            if (m_pMosaicYPyr) free(m_pMosaicYPyr);
+            return BLEND_RET_CANCELLED;
+        }
+
+        mb = csite->getMb();
+
+
+        if(FillFramePyramid(mb)!=BLEND_RET_OK)
+            return BLEND_RET_ERROR;
+
+        ProcessPyramidForThisFrame(csite, mb->vcrect, mb->brect, rect, imgMos, mb->trs, site_idx);
+
+        progress += TIME_PERCENT_BLEND/nsite;
+
+        site_idx++;
+    }
+
+
+    // Blend
+    PerformFinalBlending(imgMos, cropping_rect);
+
+    if (cropping_rect.Width() <= 0 || cropping_rect.Height() <= 0)
+    {
+        LOGE("Size of the cropping_rect is invalid - (width, height): (%d, %d)",
+                cropping_rect.Width(), cropping_rect.Height());
+        return BLEND_RET_ERROR;
+    }
+
+    if (m_pMosaicVPyr) free(m_pMosaicVPyr);
+    if (m_pMosaicUPyr) free(m_pMosaicUPyr);
+    if (m_pMosaicYPyr) free(m_pMosaicYPyr);
+
+    progress += TIME_PERCENT_FINAL;
+
+    return BLEND_RET_OK;
+}
+
+void Blend::CropFinalMosaic(YUVinfo &imgMos, MosaicRect &cropping_rect)
+{
+    int i, j, k;
+    ImageType yimg;
+    ImageType uimg;
+    ImageType vimg;
+
+
+    yimg = imgMos.Y.ptr[0];
+    uimg = imgMos.U.ptr[0];
+    vimg = imgMos.V.ptr[0];
+
+    k = 0;
+    for (j = cropping_rect.top; j <= cropping_rect.bottom; j++)
+    {
+        for (i = cropping_rect.left; i <= cropping_rect.right; i++)
+        {
+            yimg[k] = yimg[j*imgMos.Y.width+i];
+            k++;
+        }
+    }
+    for (j = cropping_rect.top; j <= cropping_rect.bottom; j++)
+    {
+       for (i = cropping_rect.left; i <= cropping_rect.right; i++)
+        {
+            yimg[k] = vimg[j*imgMos.Y.width+i];
+            k++;
+        }
+    }
+    for (j = cropping_rect.top; j <= cropping_rect.bottom; j++)
+    {
+       for (i = cropping_rect.left; i <= cropping_rect.right; i++)
+        {
+            yimg[k] = uimg[j*imgMos.Y.width+i];
+            k++;
+        }
+    }
+}
+
+int Blend::PerformFinalBlending(YUVinfo &imgMos, MosaicRect &cropping_rect)
+{
+    if (!PyramidShort::BorderExpand(m_pMosaicYPyr, m_wb.nlevs, 1) || !PyramidShort::BorderExpand(m_pMosaicUPyr, m_wb.nlevsC, 1) ||
+        !PyramidShort::BorderExpand(m_pMosaicVPyr, m_wb.nlevsC, 1))
+    {
+      LOGE("Error: Could not BorderExpand!");
+      return BLEND_RET_ERROR;
+    }
+
+    ImageTypeShort myimg;
+    ImageTypeShort muimg;
+    ImageTypeShort mvimg;
+    ImageType yimg;
+    ImageType uimg;
+    ImageType vimg;
+
+    int cx = (int)imgMos.Y.width/2;
+    int cy = (int)imgMos.Y.height/2;
+
+    // 2D boolean array that contains true wherever the mosaic image data is
+    // invalid (i.e. in the gray border).
+    bool **b = new bool*[imgMos.Y.height];
+
+    for(int j=0; j<imgMos.Y.height; j++)
+    {
+        b[j] = new bool[imgMos.Y.width];
+    }
+
+    // Copy the resulting image into the full image using the mask
+    int i, j;
+
+    yimg = imgMos.Y.ptr[0];
+    uimg = imgMos.U.ptr[0];
+    vimg = imgMos.V.ptr[0];
+
+    for (j = 0; j < imgMos.Y.height; j++)
+    {
+        myimg = m_pMosaicYPyr->ptr[j];
+        muimg = m_pMosaicUPyr->ptr[j];
+        mvimg = m_pMosaicVPyr->ptr[j];
+
+        for (i = 0; i<imgMos.Y.width; i++)
+        {
+            // A final mask was set up previously,
+            // if the value is zero skip it, otherwise replace it.
+            if (*yimg <255)
+            {
+                short value = (short) ((*myimg) >> 3);
+                if (value < 0) value = 0;
+                else if (value > 255) value = 255;
+                *yimg = (unsigned char) value;
+
+                value = (short) ((*muimg) >> 3);
+                if (value < 0) value = 0;
+                else if (value > 255) value = 255;
+                *uimg = (unsigned char) value;
+
+                value = (short) ((*mvimg) >> 3);
+                if (value < 0) value = 0;
+                else if (value > 255) value = 255;
+                *vimg = (unsigned char) value;
+
+                b[j][i] = false;
+
+            }
+            else
+            {   // set border color in here
+                *yimg = (unsigned char) 96;
+                *uimg = (unsigned char) 128;
+                *vimg = (unsigned char) 128;
+
+                b[j][i] = true;
+            }
+
+            yimg++;
+            uimg++;
+            vimg++;
+            myimg++;
+            muimg++;
+            mvimg++;
+        }
+    }
+
+    if(m_wb.horizontal)
+    {
+        //Scan through each row and increment top if the row contains any gray
+        for (j = 0; j < imgMos.Y.height; j++)
+        {
+            for (i = cropping_rect.left; i < cropping_rect.right; i++)
+            {
+                if (b[j][i])
+                {
+                    break; // to next row
+                }
+            }
+
+            if (i == cropping_rect.right)   //no gray pixel in this row!
+            {
+                cropping_rect.top = j;
+                break;
+            }
+        }
+
+        //Scan through each row and decrement bottom if the row contains any gray
+        for (j = imgMos.Y.height-1; j >= 0; j--)
+        {
+            for (i = cropping_rect.left; i < cropping_rect.right; i++)
+            {
+                if (b[j][i])
+                {
+                    break; // to next row
+                }
+            }
+
+            if (i == cropping_rect.right)   //no gray pixel in this row!
+            {
+                cropping_rect.bottom = j;
+                break;
+            }
+        }
+    }
+    else // Vertical Mosaic
+    {
+        //Scan through each column and increment left if the column contains any gray
+        for (i = 0; i < imgMos.Y.width; i++)
+        {
+            for (j = cropping_rect.top; j < cropping_rect.bottom; j++)
+            {
+                if (b[j][i])
+                {
+                    break; // to next column
+                }
+            }
+
+            if (j == cropping_rect.bottom)   //no gray pixel in this column!
+            {
+                cropping_rect.left = i;
+                break;
+            }
+        }
+
+        //Scan through each column and decrement right if the column contains any gray
+        for (i = imgMos.Y.width-1; i >= 0; i--)
+        {
+            for (j = cropping_rect.top; j < cropping_rect.bottom; j++)
+            {
+                if (b[j][i])
+                {
+                    break; // to next column
+                }
+            }
+
+            if (j == cropping_rect.bottom)   //no gray pixel in this column!
+            {
+                cropping_rect.right = i;
+                break;
+            }
+        }
+
+    }
+
+    RoundingCroppingSizeToMultipleOf8(cropping_rect);
+
+    for(int j=0; j<imgMos.Y.height; j++)
+    {
+        delete b[j];
+    }
+
+    delete b;
+
+    return BLEND_RET_OK;
+}
+
+void Blend::RoundingCroppingSizeToMultipleOf8(MosaicRect &rect) {
+    int height = rect.bottom - rect.top + 1;
+    int residue = height & 7;
+    rect.bottom -= residue;
+
+    int width = rect.right - rect.left + 1;
+    residue = width & 7;
+    rect.right -= residue;
+}
+
+void Blend::ComputeMask(CSite *csite, BlendRect &vcrect, BlendRect &brect, MosaicRect &rect, YUVinfo &imgMos, int site_idx)
+{
+    PyramidShort *dptr = m_pMosaicYPyr;
+
+    int nC = m_wb.nlevsC;
+    int l = (int) ((vcrect.lft - rect.left));
+    int b = (int) ((vcrect.bot - rect.top));
+    int r = (int) ((vcrect.rgt - rect.left));
+    int t = (int) ((vcrect.top - rect.top));
+
+    if (vcrect.lft == brect.lft)
+        l = (l <= 0) ? -BORDER : l - BORDER;
+    else if (l < -BORDER)
+        l = -BORDER;
+
+    if (vcrect.bot == brect.bot)
+        b = (b <= 0) ? -BORDER : b - BORDER;
+    else if (b < -BORDER)
+        b = -BORDER;
+
+    if (vcrect.rgt == brect.rgt)
+        r = (r >= dptr->width) ? dptr->width + BORDER - 1 : r + BORDER;
+    else if (r >= dptr->width + BORDER)
+        r = dptr->width + BORDER - 1;
+
+    if (vcrect.top == brect.top)
+        t = (t >= dptr->height) ? dptr->height + BORDER - 1 : t + BORDER;
+    else if (t >= dptr->height + BORDER)
+        t = dptr->height + BORDER - 1;
+
+    // Walk the Region of interest and populate the pyramid
+    for (int j = b; j <= t; j++)
+    {
+        int jj = j;
+        double sj = jj + rect.top;
+
+        for (int i = l; i <= r; i++)
+        {
+            int ii = i;
+            // project point and then triangulate to neighbors
+            double si = ii + rect.left;
+
+            double dself = hypotSq(csite->getVCenter().x - si, csite->getVCenter().y - sj);
+            int inMask = ((unsigned) ii < imgMos.Y.width &&
+                    (unsigned) jj < imgMos.Y.height) ? 1 : 0;
+
+            if(!inMask)
+                continue;
+
+            // scan the neighbors to see if this is a valid position
+            unsigned char mask = (unsigned char) 255;
+            SEdgeVector *ce;
+            int ecnt;
+            for (ce = csite->getNeighbor(), ecnt = csite->getNumNeighbors(); ecnt--; ce++)
+            {
+                double d1 = hypotSq(m_AllSites[ce->second].getVCenter().x - si,
+                        m_AllSites[ce->second].getVCenter().y - sj);
+                if (d1 < dself)
+                {
+                    break;
+                }
+            }
+
+            if (ecnt >= 0) continue;
+
+            imgMos.Y.ptr[jj][ii] = (unsigned char)site_idx;
+        }
+    }
+}
+
+void Blend::ProcessPyramidForThisFrame(CSite *csite, BlendRect &vcrect, BlendRect &brect, MosaicRect &rect, YUVinfo &imgMos, double trs[3][3], int site_idx)
+{
+    // Put the Region of interest (for all levels) into m_pMosaicYPyr
+    double inv_trs[3][3];
+    inv33d(trs, inv_trs);
+
+    // Process each pyramid level
+    PyramidShort *sptr = m_pFrameYPyr;
+    PyramidShort *suptr = m_pFrameUPyr;
+    PyramidShort *svptr = m_pFrameVPyr;
+
+    PyramidShort *dptr = m_pMosaicYPyr;
+    PyramidShort *duptr = m_pMosaicUPyr;
+    PyramidShort *dvptr = m_pMosaicVPyr;
+
+    int dscale = 0; // distance scale for the current level
+    int nC = m_wb.nlevsC;
+    for (int n = m_wb.nlevs; n--; dscale++, dptr++, sptr++, dvptr++, duptr++, svptr++, suptr++, nC--)
+    {
+        int l = (int) ((vcrect.lft - rect.left) / (1 << dscale));
+        int b = (int) ((vcrect.bot - rect.top) / (1 << dscale));
+        int r = (int) ((vcrect.rgt - rect.left) / (1 << dscale) + .5);
+        int t = (int) ((vcrect.top - rect.top) / (1 << dscale) + .5);
+
+        if (vcrect.lft == brect.lft)
+            l = (l <= 0) ? -BORDER : l - BORDER;
+        else if (l < -BORDER)
+            l = -BORDER;
+
+        if (vcrect.bot == brect.bot)
+            b = (b <= 0) ? -BORDER : b - BORDER;
+        else if (b < -BORDER)
+            b = -BORDER;
+
+        if (vcrect.rgt == brect.rgt)
+            r = (r >= dptr->width) ? dptr->width + BORDER - 1 : r + BORDER;
+        else if (r >= dptr->width + BORDER)
+            r = dptr->width + BORDER - 1;
+
+        if (vcrect.top == brect.top)
+            t = (t >= dptr->height) ? dptr->height + BORDER - 1 : t + BORDER;
+        else if (t >= dptr->height + BORDER)
+            t = dptr->height + BORDER - 1;
+
+        // Walk the Region of interest and populate the pyramid
+        for (int j = b; j <= t; j++)
+        {
+            int jj = (j << dscale);
+            double sj = jj + rect.top;
+
+            for (int i = l; i <= r; i++)
+            {
+                int ii = (i << dscale);
+                // project point and then triangulate to neighbors
+                double si = ii + rect.left;
+
+                int inMask = ((unsigned) ii < imgMos.Y.width &&
+                        (unsigned) jj < imgMos.Y.height) ? 1 : 0;
+
+                if(inMask && imgMos.Y.ptr[jj][ii] != site_idx &&
+                        imgMos.V.ptr[jj][ii] != site_idx &&
+                        imgMos.Y.ptr[jj][ii] != 255)
+                    continue;
+
+                // Setup weights for cross-fading
+                // Weight of the intensity already in the output pixel
+                double wt0 = 0.0;
+                // Weight of the intensity from the input pixel (current frame)
+                double wt1 = 1.0;
+
+                if (m_wb.stripType == STRIP_TYPE_WIDE)
+                {
+                    if(inMask && imgMos.Y.ptr[jj][ii] != 255)
+                    {
+                        // If not on a seam OR pyramid level exceeds
+                        // maximum level for cross-fading.
+                        if((imgMos.V.ptr[jj][ii] == 128) ||
+                            (dscale > STRIP_CROSS_FADE_MAX_PYR_LEVEL))
+                        {
+                            wt0 = 0.0;
+                            wt1 = 1.0;
+                        }
+                        else
+                        {
+                            wt0 = 1.0;
+                            wt1 = ((imgMos.Y.ptr[jj][ii] == site_idx) ?
+                                    (double)imgMos.U.ptr[jj][ii] / 100.0 :
+                                    1.0 - (double)imgMos.U.ptr[jj][ii] / 100.0);
+                        }
+                    }
+                }
+
+                // Project this mosaic point into the original frame coordinate space
+                double xx, yy;
+
+                MosaicToFrame(inv_trs, si, sj, xx, yy);
+
+                if (xx < 0.0 || yy < 0.0 || xx > width - 1.0 || yy > height - 1.0)
+                {
+                    if(inMask)
+                    {
+                        imgMos.Y.ptr[jj][ii] = 255;
+                        wt0 = 0.0f;
+                        wt1 = 1.0f;
+                    }
+                }
+
+                xx /= (1 << dscale);
+                yy /= (1 << dscale);
+
+
+                int x1 = (xx >= 0.0) ? (int) xx : (int) floor(xx);
+                int y1 = (yy >= 0.0) ? (int) yy : (int) floor(yy);
+
+                // Final destination in extended pyramid
+#ifndef LINEAR_INTERP
+                if(inSegment(x1, sptr->width, BORDER-1) &&
+                        inSegment(y1, sptr->height, BORDER-1))
+                {
+                    double xfrac = xx - x1;
+                    double yfrac = yy - y1;
+                    dptr->ptr[j][i] = (short) (wt0 * dptr->ptr[j][i] + .5 +
+                            wt1 * ciCalc(sptr, x1, y1, xfrac, yfrac));
+                    if (dvptr >= m_pMosaicVPyr && nC > 0)
+                    {
+                        duptr->ptr[j][i] = (short) (wt0 * duptr->ptr[j][i] + .5 +
+                                wt1 * ciCalc(suptr, x1, y1, xfrac, yfrac));
+                        dvptr->ptr[j][i] = (short) (wt0 * dvptr->ptr[j][i] + .5 +
+                                wt1 * ciCalc(svptr, x1, y1, xfrac, yfrac));
+                    }
+                }
+#else
+                if(inSegment(x1, sptr->width, BORDER) && inSegment(y1, sptr->height, BORDER))
+                {
+                    int x2 = x1 + 1;
+                    int y2 = y1 + 1;
+                    double xfrac = xx - x1;
+                    double yfrac = yy - y1;
+                    double y1val = sptr->ptr[y1][x1] +
+                        (sptr->ptr[y1][x2] - sptr->ptr[y1][x1]) * xfrac;
+                    double y2val = sptr->ptr[y2][x1] +
+                        (sptr->ptr[y2][x2] - sptr->ptr[y2][x1]) * xfrac;
+                    dptr->ptr[j][i] = (short) (y1val + yfrac * (y2val - y1val));
+
+                    if (dvptr >= m_pMosaicVPyr && nC > 0)
+                    {
+                        y1val = suptr->ptr[y1][x1] +
+                            (suptr->ptr[y1][x2] - suptr->ptr[y1][x1]) * xfrac;
+                        y2val = suptr->ptr[y2][x1] +
+                            (suptr->ptr[y2][x2] - suptr->ptr[y2][x1]) * xfrac;
+
+                        duptr->ptr[j][i] = (short) (y1val + yfrac * (y2val - y1val));
+
+                        y1val = svptr->ptr[y1][x1] +
+                            (svptr->ptr[y1][x2] - svptr->ptr[y1][x1]) * xfrac;
+                        y2val = svptr->ptr[y2][x1] +
+                            (svptr->ptr[y2][x2] - svptr->ptr[y2][x1]) * xfrac;
+
+                        dvptr->ptr[j][i] = (short) (y1val + yfrac * (y2val - y1val));
+                    }
+                }
+#endif
+                else
+                {
+                    clipToSegment(x1, sptr->width, BORDER);
+                    clipToSegment(y1, sptr->height, BORDER);
+
+                    dptr->ptr[j][i] = (short) (wt0 * dptr->ptr[j][i] + 0.5 +
+                            wt1 * sptr->ptr[y1][x1] );
+                    if (dvptr >= m_pMosaicVPyr && nC > 0)
+                    {
+                        dvptr->ptr[j][i] = (short) (wt0 * dvptr->ptr[j][i] +
+                                0.5 + wt1 * svptr->ptr[y1][x1] );
+                        duptr->ptr[j][i] = (short) (wt0 * duptr->ptr[j][i] +
+                                0.5 + wt1 * suptr->ptr[y1][x1] );
+                    }
+                }
+            }
+        }
+    }
+}
+
+void Blend::MosaicToFrame(double trs[3][3], double x, double y, double &wx, double &wy)
+{
+    double X, Y, z;
+    if (m_wb.theta == 0.0)
+    {
+        X = x;
+        Y = y;
+    }
+    else if (m_wb.horizontal)
+    {
+        double alpha = x * m_wb.direction / m_wb.width;
+        double length = (y - alpha * m_wb.correction) * m_wb.direction + m_wb.radius;
+        double deltaTheta = m_wb.theta * alpha;
+        double sinTheta = sin(deltaTheta);
+        double cosTheta = sqrt(1.0 - sinTheta * sinTheta) * m_wb.direction;
+        X = length * sinTheta + m_wb.x;
+        Y = length * cosTheta + m_wb.y;
+    }
+    else
+    {
+        double alpha = y * m_wb.direction / m_wb.width;
+        double length = (x - alpha * m_wb.correction) * m_wb.direction + m_wb.radius;
+        double deltaTheta = m_wb.theta * alpha;
+        double sinTheta = sin(deltaTheta);
+        double cosTheta = sqrt(1.0 - sinTheta * sinTheta) * m_wb.direction;
+        Y = length * sinTheta + m_wb.y;
+        X = length * cosTheta + m_wb.x;
+    }
+    z = ProjZ(trs, X, Y, 1.0);
+    wx = ProjX(trs, X, Y, z, 1.0);
+    wy = ProjY(trs, X, Y, z, 1.0);
+}
+
+void Blend::FrameToMosaic(double trs[3][3], double x, double y, double &wx, double &wy)
+{
+    // Project into the intermediate Mosaic coordinate system
+    double z = ProjZ(trs, x, y, 1.0);
+    double X = ProjX(trs, x, y, z, 1.0);
+    double Y = ProjY(trs, x, y, z, 1.0);
+
+    if (m_wb.theta == 0.0)
+    {
+        // No rotation, then this is all we need to do.
+        wx = X;
+        wy = Y;
+    }
+    else if (m_wb.horizontal)
+    {
+        double deltaX = X - m_wb.x;
+        double deltaY = Y - m_wb.y;
+        double length = sqrt(deltaX * deltaX + deltaY * deltaY);
+        double deltaTheta = asin(deltaX / length);
+        double alpha = deltaTheta / m_wb.theta;
+        wx = alpha * m_wb.width * m_wb.direction;
+        wy = (length - m_wb.radius) * m_wb.direction + alpha * m_wb.correction;
+    }
+    else
+    {
+        double deltaX = X - m_wb.x;
+        double deltaY = Y - m_wb.y;
+        double length = sqrt(deltaX * deltaX + deltaY * deltaY);
+        double deltaTheta = asin(deltaY / length);
+        double alpha = deltaTheta / m_wb.theta;
+        wy = alpha * m_wb.width * m_wb.direction;
+        wx = (length - m_wb.radius) * m_wb.direction + alpha * m_wb.correction;
+    }
+}
+
+
+
+// Clip the region of interest as small as possible by using the Voronoi edges of
+// the neighbors
+void Blend::ClipBlendRect(CSite *csite, BlendRect &brect)
+{
+      SEdgeVector *ce;
+      int ecnt;
+      for (ce = csite->getNeighbor(), ecnt = csite->getNumNeighbors(); ecnt--; ce++)
+      {
+        // calculate the Voronoi bisector intersection
+        const double epsilon = 1e-5;
+        double dx = (m_AllSites[ce->second].getVCenter().x - m_AllSites[ce->first].getVCenter().x);
+        double dy = (m_AllSites[ce->second].getVCenter().y - m_AllSites[ce->first].getVCenter().y);
+        double xmid = m_AllSites[ce->first].getVCenter().x + dx/2.0;
+        double ymid = m_AllSites[ce->first].getVCenter().y + dy/2.0;
+        double inter;
+
+        if (dx > epsilon)
+        {
+          // neighbor is on right
+          if ((inter = m_wb.roundoffOverlap + xmid - dy * (((dy >= 0.0) ? brect.bot : brect.top) - ymid) / dx) < brect.rgt)
+            brect.rgt = inter;
+        }
+        else if (dx < -epsilon)
+        {
+          // neighbor is on left
+          if ((inter = -m_wb.roundoffOverlap + xmid - dy * (((dy >= 0.0) ? brect.bot : brect.top) - ymid) / dx) > brect.lft)
+            brect.lft = inter;
+        }
+        if (dy > epsilon)
+        {
+          // neighbor is above
+          if ((inter = m_wb.roundoffOverlap + ymid - dx * (((dx >= 0.0) ? brect.lft : brect.rgt) - xmid) / dy) < brect.top)
+            brect.top = inter;
+        }
+        else if (dy < -epsilon)
+        {
+          // neighbor is below
+          if ((inter = -m_wb.roundoffOverlap + ymid - dx * (((dx >= 0.0) ? brect.lft : brect.rgt) - xmid) / dy) > brect.bot)
+            brect.bot = inter;
+        }
+      }
+}
+
+void Blend::FrameToMosaicRect(int width, int height, double trs[3][3], BlendRect &brect)
+{
+    // We need to walk the perimeter since the borders can be bent.
+    brect.lft = brect.bot = 2e30;
+    brect.rgt = brect.top = -2e30;
+    double xpos, ypos;
+    double lasty = height - 1.0;
+    double lastx = width - 1.0;
+    int i;
+
+    for (i = width; i--;)
+    {
+
+        FrameToMosaic(trs, (double) i, 0.0, xpos, ypos);
+        ClipRect(xpos, ypos, brect);
+        FrameToMosaic(trs, (double) i, lasty, xpos, ypos);
+        ClipRect(xpos, ypos, brect);
+    }
+    for (i = height; i--;)
+    {
+        FrameToMosaic(trs, 0.0, (double) i, xpos, ypos);
+        ClipRect(xpos, ypos, brect);
+        FrameToMosaic(trs, lastx, (double) i, xpos, ypos);
+        ClipRect(xpos, ypos, brect);
+    }
+}
+
+void Blend::SelectRelevantFrames(MosaicFrame **frames, int frames_size,
+        MosaicFrame **relevant_frames, int &relevant_frames_size)
+{
+    MosaicFrame *first = frames[0];
+    MosaicFrame *last = frames[frames_size-1];
+    MosaicFrame *mb;
+
+    double fxpos = first->trs[0][2], fypos = first->trs[1][2];
+
+    double midX = last->width / 2.0;
+    double midY = last->height / 2.0;
+    double z = ProjZ(first->trs, midX, midY, 1.0);
+    double firstX, firstY;
+    double prevX = firstX = ProjX(first->trs, midX, midY, z, 1.0);
+    double prevY = firstY = ProjY(first->trs, midX, midY, z, 1.0);
+
+    relevant_frames[0] = first; // Add first frame by default
+    relevant_frames_size = 1;
+
+    for (int i = 0; i < frames_size - 1; i++)
+    {
+        mb = frames[i];
+        double currX, currY;
+        z = ProjZ(mb->trs, midX, midY, 1.0);
+        currX = ProjX(mb->trs, midX, midY, z, 1.0);
+        currY = ProjY(mb->trs, midX, midY, z, 1.0);
+        double deltaX = currX - prevX;
+        double deltaY = currY - prevY;
+        double center2centerDist = sqrt(deltaY * deltaY + deltaX * deltaX);
+
+        if (fabs(deltaX) > STRIP_SEPARATION_THRESHOLD_PXLS ||
+                fabs(deltaY) > STRIP_SEPARATION_THRESHOLD_PXLS)
+        {
+            relevant_frames[relevant_frames_size] = mb;
+            relevant_frames_size++;
+
+            prevX = currX;
+            prevY = currY;
+        }
+    }
+
+    // Add last frame by default
+    relevant_frames[relevant_frames_size] = last;
+    relevant_frames_size++;
+}
+
+void Blend::ComputeBlendParameters(MosaicFrame **frames, int frames_size, int is360)
+{
+    // For FULL and PAN modes, we do not unwarp the mosaic into a rectangular coordinate system
+    // and so we set the theta to 0 and return.
+    if (m_wb.blendingType != BLEND_TYPE_CYLPAN && m_wb.blendingType != BLEND_TYPE_HORZ)
+    {
+        m_wb.theta = 0.0;
+        return;
+    }
+
+    MosaicFrame *first = frames[0];
+    MosaicFrame *last = frames[frames_size-1];
+    MosaicFrame *mb;
+
+    double lxpos = last->trs[0][2], lypos = last->trs[1][2];
+    double fxpos = first->trs[0][2], fypos = first->trs[1][2];
+
+    // Calculate warp to produce proper stitching.
+    // get x, y displacement
+    double midX = last->width / 2.0;
+    double midY = last->height / 2.0;
+    double z = ProjZ(first->trs, midX, midY, 1.0);
+    double firstX, firstY;
+    double prevX = firstX = ProjX(first->trs, midX, midY, z, 1.0);
+    double prevY = firstY = ProjY(first->trs, midX, midY, z, 1.0);
+
+    double arcLength, lastTheta;
+    m_wb.theta = lastTheta = arcLength = 0.0;
+
+    // Step through all the frames to compute the total arc-length of the cone
+    // swept while capturing the mosaic (in the original conical coordinate system).
+    for (int i = 0; i < frames_size; i++)
+    {
+        mb = frames[i];
+        double currX, currY;
+        z = ProjZ(mb->trs, midX, midY, 1.0);
+        currX = ProjX(mb->trs, midX, midY, z, 1.0);
+        currY = ProjY(mb->trs, midX, midY, z, 1.0);
+        double deltaX = currX - prevX;
+        double deltaY = currY - prevY;
+
+        // The arcLength is computed by summing the lengths of the chords
+        // connecting the pairwise projected image centers of the input image frames.
+        arcLength += sqrt(deltaY * deltaY + deltaX * deltaX);
+
+        if (!is360)
+        {
+            double thisTheta = asin(mb->trs[1][0]);
+            m_wb.theta += thisTheta - lastTheta;
+            lastTheta = thisTheta;
+        }
+
+        prevX = currX;
+        prevY = currY;
+    }
+
+    // Stretch this to end at the proper alignment i.e. the width of the
+    // rectangle is determined by the arcLength computed above and the cone
+    // sector angle is determined using the rotation of the last frame.
+    m_wb.width = arcLength;
+    if (is360) m_wb.theta = asin(last->trs[1][0]);
+
+    // If there is no rotation, we're done.
+    if (m_wb.theta != 0.0)
+    {
+        double dx = prevX - firstX;
+        double dy = prevY - firstY;
+
+        // If the mosaic was captured by sweeping horizontally
+        if (abs(lxpos - fxpos) > abs(lypos - fypos))
+        {
+            m_wb.horizontal = 1;
+            // Calculate radius position to make ends exactly the same Y offset
+            double radiusTheta = dx / cos(3.14159 / 2.0 - m_wb.theta);
+            m_wb.radius = dy + radiusTheta * cos(m_wb.theta);
+            if (m_wb.radius < 0.0) m_wb.radius = -m_wb.radius;
+        }
+        else
+        {
+            m_wb.horizontal = 0;
+            // Calculate radius position to make ends exactly the same Y offset
+            double radiusTheta = dy / cos(3.14159 / 2.0 - m_wb.theta);
+            m_wb.radius = dx + radiusTheta * cos(m_wb.theta);
+            if (m_wb.radius < 0.0) m_wb.radius = -m_wb.radius;
+        }
+
+        // Determine major direction
+        if (m_wb.horizontal)
+        {
+            // Horizontal strip
+            // m_wb.x,y record the origin of the rectangle coordinate system.
+            if (is360) m_wb.x = firstX;
+            else
+            {
+                if (lxpos - fxpos < 0)
+                {
+                    m_wb.x = firstX + midX;
+                    z = ProjZ(last->trs, 0.0, midY, 1.0);
+                    prevX = ProjX(last->trs, 0.0, midY, z, 1.0);
+                    prevY = ProjY(last->trs, 0.0, midY, z, 1.0);
+                }
+                else
+                {
+                    m_wb.x = firstX - midX;
+                    z = ProjZ(last->trs, last->width - 1.0, midY, 1.0);
+                    prevX = ProjX(last->trs, last->width - 1.0, midY, z, 1.0);
+                    prevY = ProjY(last->trs, last->width - 1.0, midY, z, 1.0);
+                }
+            }
+            dy = prevY - firstY;
+            if (dy < 0.0) m_wb.direction = 1.0;
+            else m_wb.direction = -1.0;
+            m_wb.y = firstY - m_wb.radius * m_wb.direction;
+            if (dy * m_wb.theta > 0.0) m_wb.width = -m_wb.width;
+        }
+        else
+        {
+            // Vertical strip
+            if (is360) m_wb.y = firstY;
+            else
+            {
+                if (lypos - fypos < 0)
+                {
+                    m_wb.x = firstY + midY;
+                    z = ProjZ(last->trs, midX, 0.0, 1.0);
+                    prevX = ProjX(last->trs, midX, 0.0, z, 1.0);
+                    prevY = ProjY(last->trs, midX, 0.0, z, 1.0);
+                }
+                else
+                {
+                    m_wb.x = firstX - midX;
+                    z = ProjZ(last->trs, midX, last->height - 1.0, 1.0);
+                    prevX = ProjX(last->trs, midX, last->height - 1.0, z, 1.0);
+                    prevY = ProjY(last->trs, midX, last->height - 1.0, z, 1.0);
+                }
+            }
+            dx = prevX - firstX;
+            if (dx < 0.0) m_wb.direction = 1.0;
+            else m_wb.direction = -1.0;
+            m_wb.x = firstX - m_wb.radius * m_wb.direction;
+            if (dx * m_wb.theta > 0.0) m_wb.width = -m_wb.width;
+        }
+
+        // Calculate the correct correction factor
+        double deltaX = prevX - m_wb.x;
+        double deltaY = prevY - m_wb.y;
+        double length = sqrt(deltaX * deltaX + deltaY * deltaY);
+        double deltaTheta = (m_wb.horizontal) ? deltaX : deltaY;
+        deltaTheta = asin(deltaTheta / length);
+        m_wb.correction = ((m_wb.radius - length) * m_wb.direction) /
+            (deltaTheta / m_wb.theta);
+    }
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic/Blend.h b/jni_mosaic/feature_mos/src/mosaic/Blend.h
new file mode 100644
index 0000000..2c7ee5c
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Blend.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Blend.h
+// $Id: Blend.h,v 1.23 2011/06/24 04:22:14 mbansal Exp $
+
+#ifndef BLEND_H
+#define BLEND_H
+
+#include "MosaicTypes.h"
+#include "Pyramid.h"
+#include "Delaunay.h"
+
+#define BLEND_RANGE_DEFAULT 6
+#define BORDER 8
+
+// Percent of total mosaicing time spent on each of the following operations
+const float TIME_PERCENT_ALIGN = 20.0;
+const float TIME_PERCENT_BLEND = 75.0;
+const float TIME_PERCENT_FINAL = 5.0;
+
+// This threshold determines the minimum separation between the image centers
+// of the input image frames for them to be accepted for blending in the
+// STRIP_TYPE_WIDE mode.
+const float STRIP_SEPARATION_THRESHOLD_PXLS = 10;
+
+// This threshold determines the number of pixels on either side of the strip
+// to cross-fade using the images contributing to each seam.
+const float STRIP_CROSS_FADE_WIDTH_PXLS = 2;
+// This specifies the maximum pyramid level to which cross-fading is applied.
+// The original image resolution is Level-0, half of that size is Level-1 and
+// so on. BLEND_RANGE_DEFAULT specifies the number of pyramid levels used by
+// the blending algorithm.
+const int STRIP_CROSS_FADE_MAX_PYR_LEVEL = 2;
+
+/**
+ *  Class for pyramid blending a mosaic.
+ */
+class Blend {
+
+public:
+
+  static const int BLEND_TYPE_NONE    = -1;
+  static const int BLEND_TYPE_FULL    = 0;
+  static const int BLEND_TYPE_PAN     = 1;
+  static const int BLEND_TYPE_CYLPAN  = 2;
+  static const int BLEND_TYPE_HORZ   = 3;
+
+  static const int STRIP_TYPE_THIN      = 0;
+  static const int STRIP_TYPE_WIDE      = 1;
+
+  static const int BLEND_RET_ERROR        = -1;
+  static const int BLEND_RET_OK           = 0;
+  static const int BLEND_RET_ERROR_MEMORY = 1;
+  static const int BLEND_RET_CANCELLED    = -2;
+
+  Blend();
+  ~Blend();
+
+  int initialize(int blendingType, int stripType, int frame_width, int frame_height);
+
+  int runBlend(MosaicFrame **frames, MosaicFrame **rframes, int frames_size, ImageType &imageMosaicYVU,
+        int &mosaicWidth, int &mosaicHeight, float &progress, bool &cancelComputation);
+
+protected:
+
+  PyramidShort *m_pFrameYPyr;
+  PyramidShort *m_pFrameUPyr;
+  PyramidShort *m_pFrameVPyr;
+
+  PyramidShort *m_pMosaicYPyr;
+  PyramidShort *m_pMosaicUPyr;
+  PyramidShort *m_pMosaicVPyr;
+
+  CDelaunay m_Triangulator;
+  CSite *m_AllSites;
+
+  BlendParams m_wb;
+
+  // Height and width of individual frames
+  int width, height;
+
+   // Height and width of mosaic
+  unsigned short Mwidth, Mheight;
+
+  // Helper functions
+  void FrameToMosaic(double trs[3][3], double x, double y, double &wx, double &wy);
+  void MosaicToFrame(double trs[3][3], double x, double y, double &wx, double &wy);
+  void FrameToMosaicRect(int width, int height, double trs[3][3], BlendRect &brect);
+  void ClipBlendRect(CSite *csite, BlendRect &brect);
+  void AlignToMiddleFrame(MosaicFrame **frames, int frames_size);
+
+  int  DoMergeAndBlend(MosaicFrame **frames, int nsite,  int width, int height, YUVinfo &imgMos, MosaicRect &rect, MosaicRect &cropping_rect, float &progress, bool &cancelComputation);
+  void ComputeMask(CSite *csite, BlendRect &vcrect, BlendRect &brect, MosaicRect &rect, YUVinfo &imgMos, int site_idx);
+  void ProcessPyramidForThisFrame(CSite *csite, BlendRect &vcrect, BlendRect &brect, MosaicRect &rect, YUVinfo &imgMos, double trs[3][3], int site_idx);
+
+  int  FillFramePyramid(MosaicFrame *mb);
+
+  // TODO: need to add documentation about the parameters
+  void ComputeBlendParameters(MosaicFrame **frames, int frames_size, int is360);
+  void SelectRelevantFrames(MosaicFrame **frames, int frames_size,
+        MosaicFrame **relevant_frames, int &relevant_frames_size);
+
+  int  PerformFinalBlending(YUVinfo &imgMos, MosaicRect &cropping_rect);
+  void CropFinalMosaic(YUVinfo &imgMos, MosaicRect &cropping_rect);
+
+private:
+   static const float LIMIT_SIZE_MULTIPLIER = 5.0f * 2.0f;
+   static const float LIMIT_HEIGHT_MULTIPLIER = 2.5f;
+   int MosaicSizeCheck(float sizeMultiplier, float heightMultiplier);
+   void RoundingCroppingSizeToMultipleOf8(MosaicRect& rect);
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/CSite.h b/jni_mosaic/feature_mos/src/mosaic/CSite.h
new file mode 100644
index 0000000..928c173
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/CSite.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// CSite.h
+// $Id: CSite.h,v 1.3 2011/06/17 13:35:47 mbansal Exp $
+
+#ifndef TRIDEL_H
+#define TRIDEL_H
+
+#include "MosaicTypes.h"
+
+typedef struct
+{
+    short first;
+    short second;
+} SEdgeVector;
+
+typedef struct
+{
+    double x;
+    double y;
+} SVec2d;
+
+class CSite
+{
+private:
+    MosaicFrame *mosaicFrame;
+    SEdgeVector *neighbor;
+    int numNeighbors;
+    SVec2d voronoiCenter;
+
+public:
+    CSite();
+    ~CSite();
+
+    inline MosaicFrame* getMb() { return mosaicFrame; }
+    inline SEdgeVector* getNeighbor() { return neighbor; }
+    inline int getNumNeighbors() { return numNeighbors; }
+    inline SVec2d& getVCenter() { return voronoiCenter; }
+    inline double X() { return voronoiCenter.x; }
+    inline double Y() { return voronoiCenter.y; }
+
+    inline void incrNumNeighbors() { numNeighbors++; }
+    inline void setNumNeighbors(int num) { numNeighbors = num; }
+    inline void setNeighbor(SEdgeVector *nb) { neighbor = nb; }
+    inline void setMb(MosaicFrame *mb) { mosaicFrame = mb; }
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Delaunay.cpp b/jni_mosaic/feature_mos/src/mosaic/Delaunay.cpp
new file mode 100644
index 0000000..0ce09fc
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Delaunay.cpp
@@ -0,0 +1,633 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// Delaunay.cpp
+// $Id: Delaunay.cpp,v 1.10 2011/06/17 13:35:48 mbansal Exp $
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include "Delaunay.h"
+
+#define QQ 9   // Optimal value as determined by testing
+#define DM 38  // 2^(1+DM/2) element sort capability. DM=38 for >10^6 elements
+#define NYL -1
+#define valid(l) ccw(orig(basel), dest(l), dest(basel))
+
+
+CDelaunay::CDelaunay()
+{
+}
+
+CDelaunay::~CDelaunay()
+{
+}
+
+// Allocate storage, construct triangulation, compute voronoi corners
+int CDelaunay::triangulate(SEdgeVector **edges, int n_sites, int width, int height)
+{
+  EdgePointer cep;
+
+  deleteAllEdges();
+  buildTriangulation(n_sites);
+  cep = consolidateEdges();
+  *edges = ev;
+
+  // Note: construction_list will change ev
+  return constructList(cep, width, height);
+}
+
+// builds delaunay triangulation
+void CDelaunay::buildTriangulation(int size)
+{
+  int i, rows;
+  EdgePointer lefte, righte;
+
+  rows = (int)( 0.5 + sqrt( (double) size / log( (double) size )));
+
+  // Sort the pointers by  x-coordinate of site
+  for ( i=0 ; i < size ; i++ ) {
+    sp[i] = (SitePointer) i;
+  }
+
+  spsortx( sp, 0, size-1 );
+  build( 0, size-1, &lefte, &righte, rows );
+  oneBndryEdge = lefte;
+}
+
+// Recursive Delaunay Triangulation Procedure
+// Contains modifications for axis-switching division.
+void CDelaunay::build(int lo, int hi, EdgePointer *le, EdgePointer *re, int rows)
+{
+  EdgePointer a, b, c, ldo, rdi, ldi, rdo, maxx, minx;
+  int split, lowrows;
+  int low, high;
+  SitePointer s1, s2, s3;
+  low = lo;
+  high = hi;
+
+  if ( low < (high-2) ) {
+    // more than three elements; do recursion
+    minx = sp[low];
+    maxx = sp[high];
+    if (rows == 1) {    // time to switch axis of division
+      spsorty( sp, low, high);
+      rows = 65536;
+    }
+    lowrows = rows/2;
+    split = low - 1 + (int)
+      (0.5 + ((double)(high-low+1) * ((double)lowrows / (double)rows)));
+    build( low, split, &ldo, &ldi, lowrows );
+    build( split+1, high, &rdi, &rdo, (rows-lowrows) );
+    doMerge(&ldo, ldi, rdi, &rdo);
+    while (orig(ldo) != minx) {
+      ldo = rprev(ldo);
+    }
+    while (orig(rdo) != maxx) {
+      rdo = (SitePointer) lprev(rdo);
+    }
+    *le = ldo;
+    *re = rdo;
+  }
+  else if (low >= (high - 1)) { // two or one points
+    a = makeEdge(sp[low], sp[high]);
+    *le = a;
+    *re = (EdgePointer) sym(a);
+  } else { // three points
+    // 3 cases: triangles of 2 orientations, and 3 points on a line
+    a = makeEdge((s1 = sp[low]), (s2 = sp[low+1]));
+    b = makeEdge(s2, (s3 = sp[high]));
+    splice((EdgePointer) sym(a), b);
+    if (ccw(s1, s3, s2)) {
+      c = connectLeft(b, a);
+      *le = (EdgePointer) sym(c);
+      *re = c;
+    } else {
+      *le = a;
+      *re = (EdgePointer) sym(b);
+      if (ccw(s1, s2, s3)) {
+        // not colinear
+        c = connectLeft(b, a);
+      }
+    }
+  }
+}
+
+// Quad-edge manipulation primitives
+EdgePointer CDelaunay::makeEdge(SitePointer origin, SitePointer destination)
+{
+  EdgePointer temp, ans;
+  temp = allocEdge();
+  ans = temp;
+
+  onext(temp) = ans;
+  orig(temp) = origin;
+  onext(++temp) = (EdgePointer) (ans + 3);
+  onext(++temp) = (EdgePointer) (ans + 2);
+  orig(temp) = destination;
+  onext(++temp) = (EdgePointer) (ans + 1);
+
+  return(ans);
+}
+
+void CDelaunay::splice(EdgePointer a, EdgePointer b)
+{
+  EdgePointer alpha, beta, temp;
+  alpha = (EdgePointer) rot(onext(a));
+  beta = (EdgePointer) rot(onext(b));
+  temp = onext(alpha);
+  onext(alpha) = onext(beta);
+  onext(beta) = temp;
+  temp = onext(a);
+  onext(a) = onext(b);
+  onext(b) = temp;
+}
+
+EdgePointer CDelaunay::connectLeft(EdgePointer a, EdgePointer b)
+{
+  EdgePointer ans;
+  ans = makeEdge(dest(a), orig(b));
+  splice(ans, (EdgePointer) lnext(a));
+  splice((EdgePointer) sym(ans), b);
+  return(ans);
+}
+
+EdgePointer CDelaunay::connectRight(EdgePointer a, EdgePointer b)
+{
+  EdgePointer ans;
+  ans = makeEdge(dest(a), orig(b));
+  splice(ans, (EdgePointer) sym(a));
+  splice((EdgePointer) sym(ans), (EdgePointer) oprev(b));
+  return(ans);
+}
+
+// disconnects e from the rest of the structure and destroys it
+void CDelaunay::deleteEdge(EdgePointer e)
+{
+  splice(e, (EdgePointer) oprev(e));
+  splice((EdgePointer) sym(e), (EdgePointer) oprev(sym(e)));
+  freeEdge(e);
+}
+
+//
+// Overall storage allocation
+//
+
+// Quad-edge storage allocation
+CSite *CDelaunay::allocMemory(int n)
+{
+  unsigned int size;
+
+  size = ((sizeof(CSite) + sizeof(SitePointer)) * n +
+          (sizeof(SitePointer) + sizeof(EdgePointer)) * 12
+          ) * n;
+  if (!(sa = (CSite*) malloc(size))) {
+    return NULL;
+  }
+  sp = (SitePointer *) (sa + n);
+  ev = (SEdgeVector *) (org = sp + n);
+  next = (EdgePointer *) (org + 12 * n);
+  ei = (struct EDGE_INFO *) (next + 12 * n);
+  return sa;
+}
+
+void CDelaunay::freeMemory()
+{
+  if (sa) {
+    free(sa);
+    sa = (CSite*)NULL;
+  }
+}
+
+//
+// Edge storage management
+//
+
+void CDelaunay::deleteAllEdges()
+{
+    nextEdge = 0;
+    availEdge = NYL;
+}
+
+EdgePointer CDelaunay::allocEdge()
+{
+  EdgePointer ans;
+
+  if (availEdge == NYL) {
+    ans = nextEdge, nextEdge += 4;
+  } else {
+    ans = availEdge, availEdge = onext(availEdge);
+  }
+  return(ans);
+}
+
+void CDelaunay::freeEdge(EdgePointer e)
+{
+  e ^= e & 3;
+  onext(e) = availEdge;
+  availEdge = e;
+}
+
+EdgePointer CDelaunay::consolidateEdges()
+{
+  EdgePointer e;
+  int i,j;
+
+  while (availEdge != NYL) {
+    nextEdge -= 4; e = availEdge; availEdge = onext(availEdge);
+
+    if (e==nextEdge) {
+      continue; // the one deleted was the last one anyway
+    }
+    if ((oneBndryEdge&~3) == nextEdge) {
+      oneBndryEdge = (EdgePointer) (e | (oneBndryEdge&3));
+    }
+    for (i=0,j=3; i<4; i++,j=rot(j)) {
+      onext(e+i) = onext(nextEdge+i);
+      onext(rot(onext(e+i))) = (EdgePointer) (e+j);
+    }
+  }
+  return nextEdge;
+}
+
+//
+// Sorting Routines
+//
+
+int CDelaunay::xcmpsp(int i, int j)
+{
+  double d = sa[(i>=0)?sp[i]:sp1].X() - sa[(j>=0)?sp[j]:sp1].X();
+  if ( d > 0. ) {
+    return 1;
+  }
+  if ( d < 0. ) {
+    return -1;
+  }
+  d = sa[(i>=0)?sp[i]:sp1].Y() - sa[(j>=0)?sp[j]:sp1].Y();
+  if ( d > 0. ) {
+    return 1;
+  }
+  if ( d < 0. ) {
+    return -1;
+  }
+  return 0;
+}
+
+int CDelaunay::ycmpsp(int i, int j)
+{
+  double d = sa[(i>=0)?sp[i]:sp1].Y() - sa[(j>=0)?sp[j]:sp1].Y();
+  if ( d > 0. ) {
+    return 1;
+  }
+  if ( d < 0. ) {
+    return -1;
+  }
+  d = sa[(i>=0)?sp[i]:sp1].X() - sa[(j>=0)?sp[j]:sp1].X();
+  if ( d > 0. ) {
+    return 1;
+  }
+  if ( d < 0. ) {
+    return -1;
+  }
+  return 0;
+}
+
+int CDelaunay::cmpev(int i, int j)
+{
+  return (ev[i].first - ev[j].first);
+}
+
+void CDelaunay::swapsp(int i, int j)
+{
+  int t;
+  t = (i>=0) ? sp[i] : sp1;
+
+  if (i>=0) {
+    sp[i] = (j>=0)?sp[j]:sp1;
+  } else {
+    sp1 = (j>=0)?sp[j]:sp1;
+  }
+
+  if (j>=0) {
+    sp[j] = (SitePointer) t;
+  } else {
+    sp1 = (SitePointer) t;
+  }
+}
+
+void CDelaunay::swapev(int i, int j)
+{
+  SEdgeVector temp;
+
+  temp = ev[i];
+  ev[i] = ev[j];
+  ev[j] = temp;
+}
+
+void CDelaunay::copysp(int i, int j)
+{
+  if (j>=0) {
+    sp[j] = (i>=0)?sp[i]:sp1;
+  } else {
+    sp1 = (i>=0)?sp[i]:sp1;
+  }
+}
+
+void CDelaunay::copyev(int i, int j)
+{
+  ev[j] = ev[i];
+}
+
+void CDelaunay::spsortx(SitePointer *sp_in, int low, int high)
+{
+  sp = sp_in;
+  rcssort(low,high,-1,&CDelaunay::xcmpsp,&CDelaunay::swapsp,&CDelaunay::copysp);
+}
+
+void CDelaunay::spsorty(SitePointer *sp_in, int low, int high )
+{
+  sp = sp_in;
+  rcssort(low,high,-1,&CDelaunay::ycmpsp,&CDelaunay::swapsp,&CDelaunay::copysp);
+}
+
+void CDelaunay::rcssort(int lowelt, int highelt, int temp,
+                    int (CDelaunay::*comparison)(int,int),
+                    void (CDelaunay::*swap)(int,int),
+                    void (CDelaunay::*copy)(int,int))
+{
+  int m,sij,si,sj,sL,sk;
+  int stack[DM];
+
+  if (highelt-lowelt<=1) {
+    return;
+  }
+  if (highelt-lowelt>QQ) {
+    m = 0;
+    si = lowelt; sj = highelt;
+    for (;;) { // partition [si,sj] about median-of-3.
+      sij = (sj+si) >> 1;
+
+      // Now to sort elements si,sij,sj into order & set temp=their median
+      if ( (this->*comparison)( si,sij ) > 0 ) {
+        (this->*swap)( si,sij );
+      }
+      if ( (this->*comparison)( sij,sj ) > 0 ) {
+        (this->*swap)( sj,sij );
+        if ( (this->*comparison)( si,sij ) > 0 ) {
+          (this->*swap)( si,sij );
+        }
+      }
+      (this->*copy)( sij,temp );
+
+      // Now to partition into elements <=temp, >=temp, and ==temp.
+      sk = si; sL = sj;
+      do {
+        do {
+          sL--;
+        } while( (this->*comparison)( sL,temp ) > 0 );
+        do {
+          sk++;
+        } while( (this->*comparison)( temp,sk ) > 0 );
+        if ( sk < sL ) {
+          (this->*swap)( sL,sk );
+        }
+      } while(sk <= sL);
+
+      // Now to recurse on shorter partition, store longer partition on stack
+      if ( sL-si > sj-sk ) {
+        if ( sL-si < QQ ) {
+          if( m==0 ) {
+            break;  // empty stack && both partitions < QQ so break
+          } else {
+            sj = stack[--m];
+            si = stack[--m];
+          }
+        }
+        else {
+          if ( sj-sk < QQ ) {
+            sj = sL;
+          } else {
+            stack[m++] = si;
+            stack[m++] = sL;
+            si = sk;
+          }
+        }
+      }
+      else {
+        if ( sj-sk < QQ ) {
+          if ( m==0 ) {
+            break; // empty stack && both partitions < QQ so break
+          } else {
+            sj = stack[--m];
+            si = stack[--m];
+          }
+        }
+        else {
+          if ( sL-si < QQ ) {
+            si = sk;
+          } else {
+            stack[m++] = sk;
+            stack[m++] = sj;
+            sj = sL;
+          }
+        }
+      }
+    }
+  }
+
+  // Now for 0 or Data bounded  "straight insertion" sort of [0,nels-1]; if it is
+  // known that el[-1] = -INF, then can omit the "sk>=0" test and save time.
+  for (si=lowelt; si<highelt; si++) {
+    if ( (this->*comparison)( si,si+1 ) > 0 ) {
+      (this->*copy)( si+1,temp );
+      sj = sk = si;
+      sj++;
+      do {
+        (this->*copy)( sk,sj );
+        sj = sk;
+        sk--;
+      } while ( (this->*comparison)( sk,temp ) > 0 && sk>=lowelt );
+      (this->*copy)( temp,sj );
+    }
+  }
+}
+
+//
+// Geometric primitives
+//
+
+// incircle, as in the Guibas-Stolfi paper.
+int CDelaunay::incircle(SitePointer a, SitePointer b, SitePointer c, SitePointer d)
+{
+  double adx, ady, bdx, bdy, cdx, cdy, dx, dy, nad, nbd, ncd;
+  dx = sa[d].X();
+  dy = sa[d].Y();
+  adx = sa[a].X() - dx;
+  ady = sa[a].Y() - dy;
+  bdx = sa[b].X() - dx;
+  bdy = sa[b].Y() - dy;
+  cdx = sa[c].X() - dx;
+  cdy = sa[c].Y() - dy;
+  nad = adx*adx+ady*ady;
+  nbd = bdx*bdx+bdy*bdy;
+  ncd = cdx*cdx+cdy*cdy;
+  return( (0.0 < (nad * (bdx * cdy - bdy * cdx)
+                  + nbd * (cdx * ady - cdy * adx)
+                  + ncd * (adx * bdy - ady * bdx))) ? TRUE : FALSE );
+}
+
+// TRUE iff A, B, C form a counterclockwise oriented triangle
+int CDelaunay::ccw(SitePointer a, SitePointer b, SitePointer c)
+{
+  int result;
+
+  double ax = sa[a].X();
+  double bx = sa[b].X();
+  double cx = sa[c].X();
+  double ay = sa[a].Y();
+  double by = sa[b].Y();
+  double cy = sa[c].Y();
+
+  double val = (ax - cx)*(by - cy) - (bx - cx)*(ay - cy);
+  if ( val > 0.0) {
+    return true;
+  }
+
+  return false;
+}
+
+//
+// The Merge Procedure.
+//
+
+void CDelaunay::doMerge(EdgePointer *ldo, EdgePointer ldi, EdgePointer rdi, EdgePointer *rdo)
+{
+  int rvalid, lvalid;
+  EdgePointer basel,lcand,rcand,t;
+
+  for (;;) {
+    while (ccw(orig(ldi), dest(ldi), orig(rdi))) {
+        ldi = (EdgePointer) lnext(ldi);
+    }
+    if (ccw(dest(rdi), orig(rdi), orig(ldi))) {
+        rdi = (EdgePointer)rprev(rdi);
+    } else {
+      break;
+    }
+  }
+
+  basel = connectLeft((EdgePointer) sym(rdi), ldi);
+  lcand = rprev(basel);
+  rcand = (EdgePointer) oprev(basel);
+  if (orig(basel) == orig(*rdo)) {
+    *rdo = basel;
+  }
+  if (dest(basel) == orig(*ldo)) {
+    *ldo = (EdgePointer) sym(basel);
+  }
+
+  for (;;) {
+#if 1
+    if (valid(t=onext(lcand))) {
+#else
+    t = (EdgePointer)onext(lcand);
+    if (valid(basel, t)) {
+#endif
+      while (incircle(dest(lcand), dest(t), orig(lcand), orig(basel))) {
+        deleteEdge(lcand);
+        lcand = t;
+        t = onext(lcand);
+      }
+    }
+#if 1
+    if (valid(t=(EdgePointer)oprev(rcand))) {
+#else
+    t = (EdgePointer)oprev(rcand);
+    if (valid(basel, t)) {
+#endif
+      while (incircle(dest(t), dest(rcand), orig(rcand), dest(basel))) {
+        deleteEdge(rcand);
+        rcand = t;
+        t = (EdgePointer)oprev(rcand);
+      }
+    }
+
+#if 1
+    lvalid = valid(lcand);
+    rvalid = valid(rcand);
+#else
+    lvalid = valid(basel, lcand);
+    rvalid = valid(basel, rcand);
+#endif
+    if ((! lvalid) && (! rvalid)) {
+      return;
+    }
+
+    if (!lvalid ||
+        (rvalid && incircle(dest(lcand), orig(lcand), orig(rcand), dest(rcand)))) {
+      basel = connectLeft(rcand, (EdgePointer) sym(basel));
+      rcand = (EdgePointer) lnext(sym(basel));
+    } else {
+      basel = (EdgePointer) sym(connectRight(lcand, basel));
+      lcand = rprev(basel);
+    }
+  }
+}
+
+int CDelaunay::constructList(EdgePointer last, int width, int height)
+{
+  int c, i;
+  EdgePointer curr, src, nex;
+  SEdgeVector *currv, *prevv;
+
+  c = (int) ((curr = (EdgePointer) ((last & ~3))) >> 1);
+
+  for (last -= 4; last >= 0; last -= 4) {
+    src = orig(last);
+    nex = dest(last);
+    orig(--curr) = src;
+    orig(--curr) = nex;
+    orig(--curr) = nex;
+    orig(--curr) = src;
+  }
+  rcssort(0, c - 1, -1, &CDelaunay::cmpev, &CDelaunay::swapev, &CDelaunay::copyev);
+
+  // Throw out any edges that are too far apart
+  currv = prevv = ev;
+  for (i = c; i--; currv++) {
+      if ((int) fabs(sa[currv->first].getVCenter().x - sa[currv->second].getVCenter().x) <= width &&
+          (int) fabs(sa[currv->first].getVCenter().y - sa[currv->second].getVCenter().y) <= height) {
+          *(prevv++) = *currv;
+      } else {
+        c--;
+      }
+  }
+  return c;
+}
+
+// Fill in site neighbor information
+void CDelaunay::linkNeighbors(SEdgeVector *edge, int nedge, int nsite)
+{
+  int i;
+
+  for (i = 0; i < nsite; i++) {
+    sa[i].setNeighbor(edge);
+    sa[i].setNumNeighbors(0);
+    for (; edge->first == i && nedge; edge++, nedge--) {
+      sa[i].incrNumNeighbors();
+    }
+  }
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic/Delaunay.h b/jni_mosaic/feature_mos/src/mosaic/Delaunay.h
new file mode 100644
index 0000000..7a450b5
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Delaunay.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// Delaunay.h
+// $Id: Delaunay.h,v 1.9 2011/06/17 13:35:48 mbansal Exp $
+
+#ifndef DELAUNAY_H
+#define DELAUNAY_H
+#include <stdio.h>
+#include <math.h>
+#include "CSite.h"
+#include "EdgePointerUtil.h"
+
+#ifndef TRUE
+#define TRUE 1==1
+#define FALSE 0==1
+#endif
+
+//******************************************************************************
+// Reference for Quad-edge data structure:
+//
+// Leonidas Guibas and Jorge Stolfi, "Primitives for the manipulation of general
+//     subdivisions and the computations of Voronoi diagrams",
+//     ACM Transactions on Graphics 4, 74-123 (1985).
+//
+//******************************************************************************
+
+//
+// Common data structures
+//
+
+typedef short SitePointer;
+typedef short TrianglePointer;
+
+class CDelaunay
+{
+private:
+  CSite *sa;
+  EdgePointer oneBndryEdge;
+  EdgePointer *next;
+  SitePointer *org;
+  struct EDGE_INFO *ei;
+  SitePointer *sp;
+  SEdgeVector *ev;
+
+  SitePointer sp1;
+  EdgePointer nextEdge;
+  EdgePointer availEdge;
+
+private:
+  void build(int lo, int hi, EdgePointer *le, EdgePointer *re, int rows);
+  void buildTriangulation(int size);
+
+  EdgePointer allocEdge();
+  void freeEdge(EdgePointer e);
+
+  EdgePointer makeEdge(SitePointer origin, SitePointer destination);
+  void deleteEdge(EdgePointer e);
+
+  void splice(EdgePointer, EdgePointer);
+  EdgePointer consolidateEdges();
+  void deleteAllEdges();
+
+  void spsortx(SitePointer *, int, int);
+  void spsorty(SitePointer *, int, int);
+
+  int cmpev(int i, int j);
+  int xcmpsp(int i, int j);
+  int ycmpsp(int i, int j);
+
+  void swapsp(int i, int j);
+  void swapev(int i, int j);
+
+  void copysp(int i, int j);
+  void copyev(int i, int j);
+
+  void rcssort(int lowelt, int highelt, int temp,
+                 int (CDelaunay::*comparison)(int,int),
+                 void (CDelaunay::*swap)(int,int),
+                 void (CDelaunay::*copy)(int,int));
+
+  void doMerge(EdgePointer *ldo, EdgePointer ldi, EdgePointer rdi, EdgePointer *rdo);
+  EdgePointer connectLeft(EdgePointer a, EdgePointer b);
+  EdgePointer connectRight(EdgePointer a, EdgePointer b);
+  int ccw(SitePointer a, SitePointer b, SitePointer c);
+  int incircle(SitePointer a, SitePointer b, SitePointer c, SitePointer d);
+  int constructList(EdgePointer e, int width, int height);
+
+public:
+  CDelaunay();
+  ~CDelaunay();
+
+  CSite *allocMemory(int nsite);
+  void freeMemory();
+  int triangulate(SEdgeVector **edge, int nsite, int width, int height);
+  void linkNeighbors(SEdgeVector *edge, int nedge, int nsite);
+};
+
+#define onext(a) next[a]
+#define oprev(a) rot(onext(rot(a)))
+#define lnext(a) rot(onext(rotinv(a)))
+#define lprev(a) sym(onext(a))
+#define rnext(a) rotinv(onext(rot(a)))
+#define rprev(a) onext(sym(a))
+#define dnext(a) sym(onext(sym(a)))
+#define dprev(a) rotinv(onext(rotinv(a)))
+
+#define orig(a) org[a]
+#define dest(a) orig(sym(a))
+#define left(a) orig(rotinv(a))
+#define right(a) orig(rot(a))
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/EdgePointerUtil.h b/jni_mosaic/feature_mos/src/mosaic/EdgePointerUtil.h
new file mode 100644
index 0000000..fad05d7
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/EdgePointerUtil.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef _EDGEPOINTERUTIL_H_
+#define _EDGEPOINTERUTIL_H_
+
+typedef short EdgePointer;
+
+inline EdgePointer sym(EdgePointer a)
+{
+  return a ^ 2;
+}
+
+inline EdgePointer rot(EdgePointer a)
+{
+  return (((a) + 1) & 3) | ((a) & ~3);
+}
+
+inline EdgePointer rotinv(EdgePointer a)
+{
+  return (((a) + 3) & 3) | ((a) & ~3);
+}
+
+#endif //_EDGEPOINTERUTIL_H_
diff --git a/jni_mosaic/feature_mos/src/mosaic/Geometry.h b/jni_mosaic/feature_mos/src/mosaic/Geometry.h
new file mode 100644
index 0000000..0efa0f4
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Geometry.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/////////////////////////////
+// Geometry.h
+// $Id: Geometry.h,v 1.2 2011/06/17 13:35:48 mbansal Exp $
+
+#pragma once
+#include "MosaicTypes.h"
+
+///////////////////////////////////////////////////////////////
+///////////////// BEG GLOBAL ROUTINES /////////////////////////
+///////////////////////////////////////////////////////////////
+
+
+inline double hypotSq(double a, double b)
+{
+    return ((a)*(a)+(b)*(b));
+}
+
+inline void ClipRect(double x, double y, BlendRect &brect)
+{
+    if (y < brect.bot) brect.bot = y;
+    if (y > brect.top) brect.top = y;
+    if (x < brect.lft) brect.lft = x;
+    if (x > brect.rgt) brect.rgt = x;
+}
+
+inline void ClipRect(BlendRect rrect, BlendRect &brect)
+{
+    if (rrect.bot < brect.bot) brect.bot = rrect.bot;
+    if (rrect.top > brect.top) brect.top = rrect.top;
+    if (rrect.lft < brect.lft) brect.lft = rrect.lft;
+    if (rrect.rgt > brect.rgt) brect.rgt = rrect.rgt;
+}
+
+// Clip x to be within [-border,width+border-1]
+inline void clipToSegment(int &x, int width, int border)
+{
+    if(x < -border)
+        x = -border;
+    else if(x >= width+border)
+        x = width + border - 1;
+}
+
+// Return true if x within [-border,width+border-1]
+inline bool inSegment(int x, int width, int border)
+{
+    return (x >= -border && x < width + border - 1);
+}
+
+inline void FindTriangleCentroid(double x0, double y0, double x1, double y1,
+                                    double x2, double y2,
+                                    double &mass, double &centX, double &centY)
+{
+    // Calculate the centroid of the triangle
+    centX = (x0 + x1 + x2) / 3.0;
+    centY = (y0 + y1 + y2) / 3.0;
+
+    // Calculate 2*Area for the triangle
+    if (y0 == y2)
+    {
+        if (x0 == x1)
+        {
+            mass = fabs((y1 - y0) * (x2 - x0)); // Special case 1a
+        }
+        else
+        {
+            mass = fabs((y1 - y0) * (x1 - x0)); // Special case 1b
+        }
+    }
+    else if (x0 == x2)
+    {
+        if (x0 == x1)
+        {
+            mass = fabs((x2 - x0) * (y2 - y0)); // Special case 2a
+        }
+        else
+        {
+            mass = fabs((x1 - x0) * (y2 - y0)); // Special case 2a
+        }
+    }
+    else if (x1 == x2)
+    {
+        mass = fabs((x1 - x0) * (y2 - y0)); // Special case 3
+    }
+    else
+    {
+        // Calculate line equation from x0,y0 to x2,y2
+        double dx = x2 - x0;
+        double dy = y2 - y0;
+        // Calculate the length of the side
+        double len1 = sqrt(dx * dx + dy * dy);
+        double m1 = dy / dx;
+        double b1 = y0 - m1 * x0;
+        // Calculate the line that goes through x1,y1 and is perpendicular to
+        // the other line
+        double m2 = 1.0 / m1;
+        double b2 = y1 - m2 * x1;
+        // Calculate the intersection of the two lines
+        if (fabs( m1 - m2 ) > 1.e-6)
+        {
+            double x = (b2 - b1) / (m1 - m2);
+            // the mass is the base * height
+            dx = x1 - x;
+            dy = y1 - m1 * x + b1;
+            mass = len1 * sqrt(dx * dx + dy * dy);
+        }
+        else
+        {
+            mass = fabs( (y1 - y0) * (x2 - x0) );
+        }
+    }
+}
+
+inline void FindQuadCentroid(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3,
+                                     double &centX, double &centY)
+
+{
+    // To find the centroid:
+    // 1) Divide the quadrilateral into two triangles by scribing a diagonal
+    // 2) Calculate the centroid of each triangle (the intersection of the angle bisections).
+    // 3) Find the centroid of the quad by weighting each triangle centroids by their area.
+
+    // Calculate the corner points
+    double z;
+
+    // The quad is split from x0,y0 to x2,y2
+    double mass1, mass2, cent1x, cent2x, cent1y, cent2y;
+    FindTriangleCentroid(x0, y0, x1, y1, x2, y2, mass1, cent1x, cent1y);
+    FindTriangleCentroid(x0, y0, x3, y3, x2, y2, mass2, cent2x, cent2y);
+
+    // determine position of quad centroid
+    z = mass2 / (mass1 + mass2);
+    centX = cent1x + (cent2x - cent1x) * z;
+    centY = cent1y + (cent2y - cent1y) * z;
+}
+
+///////////////////////////////////////////////////////////////
+////////////////// END GLOBAL ROUTINES ////////////////////////
+///////////////////////////////////////////////////////////////
+
+
diff --git a/jni_mosaic/feature_mos/src/mosaic/ImageUtils.cpp b/jni_mosaic/feature_mos/src/mosaic/ImageUtils.cpp
new file mode 100644
index 0000000..6d0aac0
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/ImageUtils.cpp
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// ImageUtils.cpp
+// $Id: ImageUtils.cpp,v 1.12 2011/06/17 13:35:48 mbansal Exp $
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#include "ImageUtils.h"
+
+void ImageUtils::rgba2yvu(ImageType out, ImageType in, int width, int height)
+{
+  int r,g,b, a;
+  ImageType yimg = out;
+  ImageType vimg = yimg + width*height;
+  ImageType uimg = vimg + width*height;
+  ImageType image = in;
+
+  for (int ii = 0; ii < height; ii++) {
+    for (int ij = 0; ij < width; ij++) {
+      r = (*image++);
+      g = (*image++);
+      b = (*image++);
+      a = (*image++);
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      int val = (int) (REDY * r + GREENY * g + BLUEY * b) / 1000 + 16;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(yimg) = val;
+
+      val = (int) (REDV * r - GREENV * g - BLUEV * b) / 1000 + 128;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(vimg) = val;
+
+      val = (int) (-REDU * r - GREENU * g + BLUEU * b) / 1000 + 128;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(uimg) = val;
+
+      yimg++;
+      uimg++;
+      vimg++;
+    }
+  }
+}
+
+
+void ImageUtils::rgb2yvu(ImageType out, ImageType in, int width, int height)
+{
+  int r,g,b;
+  ImageType yimg = out;
+  ImageType vimg = yimg + width*height;
+  ImageType uimg = vimg + width*height;
+  ImageType image = in;
+
+  for (int ii = 0; ii < height; ii++) {
+    for (int ij = 0; ij < width; ij++) {
+      r = (*image++);
+      g = (*image++);
+      b = (*image++);
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      int val = (int) (REDY * r + GREENY * g + BLUEY * b) / 1000 + 16;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(yimg) = val;
+
+      val = (int) (REDV * r - GREENV * g - BLUEV * b) / 1000 + 128;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(vimg) = val;
+
+      val = (int) (-REDU * r - GREENU * g + BLUEU * b) / 1000 + 128;
+      if (val < 0) val = 0;
+      if (val > 255) val = 255;
+      *(uimg) = val;
+
+      yimg++;
+      uimg++;
+      vimg++;
+    }
+  }
+}
+
+ImageType ImageUtils::rgb2gray(ImageType in, int width, int height)
+{
+  int r,g,b, nr, ng, nb, val;
+  ImageType gray = NULL;
+  ImageType image = in;
+  ImageType out = ImageUtils::allocateImage(width, height, 1);
+  ImageType outCopy = out;
+
+  for (int ii = 0; ii < height; ii++) {
+    for (int ij = 0; ij < width; ij++) {
+      r = (*image++);
+      g = (*image++);
+      b = (*image++);
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      (*outCopy) = ( 0.3*r + 0.59*g + 0.11*b);
+
+      outCopy++;
+    }
+  }
+
+  return out;
+}
+
+ImageType ImageUtils::rgb2gray(ImageType out, ImageType in, int width, int height)
+{
+  int r,g,b, nr, ng, nb, val;
+  ImageType gray = out;
+  ImageType image = in;
+  ImageType outCopy = out;
+
+  for (int ii = 0; ii < height; ii++) {
+    for (int ij = 0; ij < width; ij++) {
+      r = (*image++);
+      g = (*image++);
+      b = (*image++);
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      (*outCopy) = ( 0.3*r + 0.59*g + 0.11*b);
+
+      outCopy++;
+    }
+  }
+
+  return out;
+
+}
+
+ImageType *ImageUtils::imageTypeToRowPointers(ImageType in, int width, int height)
+{
+  int i;
+  int m_h = height;
+  int m_w = width;
+
+  ImageType *m_rows = new ImageType[m_h];
+
+  for (i=0;i<m_h;i++) {
+    m_rows[i] = &in[(m_w)*i];
+  }
+  return m_rows;
+}
+
+void ImageUtils::yvu2rgb(ImageType out, ImageType in, int width, int height)
+{
+  int y,v,u, r, g, b;
+  unsigned char *yimg = in;
+  unsigned char *vimg = yimg + width*height;
+  unsigned char *uimg = vimg + width*height;
+  unsigned char *image = out;
+
+  for (int i = 0; i < height; i++) {
+    for (int j = 0; j < width; j++) {
+
+      y = (*yimg);
+      v = (*vimg);
+      u = (*uimg);
+
+      if (y < 0) y = 0;
+      if (y > 255) y = 255;
+      if (u < 0) u = 0;
+      if (u > 255) u = 255;
+      if (v < 0) v = 0;
+      if (v > 255) v = 255;
+
+      b = (int) ( 1.164*(y - 16) + 2.018*(u-128));
+      g = (int) ( 1.164*(y - 16) - 0.813*(v-128) - 0.391*(u-128));
+      r = (int) ( 1.164*(y - 16) + 1.596*(v-128));
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      *(image++) = r;
+      *(image++) = g;
+      *(image++) = b;
+
+      yimg++;
+      uimg++;
+      vimg++;
+
+    }
+  }
+}
+
+void ImageUtils::yvu2bgr(ImageType out, ImageType in, int width, int height)
+{
+  int y,v,u, r, g, b;
+  unsigned char *yimg = in;
+  unsigned char *vimg = yimg + width*height;
+  unsigned char *uimg = vimg + width*height;
+  unsigned char *image = out;
+
+  for (int i = 0; i < height; i++) {
+    for (int j = 0; j < width; j++) {
+
+      y = (*yimg);
+      v = (*vimg);
+      u = (*uimg);
+
+      if (y < 0) y = 0;
+      if (y > 255) y = 255;
+      if (u < 0) u = 0;
+      if (u > 255) u = 255;
+      if (v < 0) v = 0;
+      if (v > 255) v = 255;
+
+      b = (int) ( 1.164*(y - 16) + 2.018*(u-128));
+      g = (int) ( 1.164*(y - 16) - 0.813*(v-128) - 0.391*(u-128));
+      r = (int) ( 1.164*(y - 16) + 1.596*(v-128));
+
+      if (r < 0) r = 0;
+      if (r > 255) r = 255;
+      if (g < 0) g = 0;
+      if (g > 255) g = 255;
+      if (b < 0) b = 0;
+      if (b > 255) b = 255;
+
+      *(image++) = b;
+      *(image++) = g;
+      *(image++) = r;
+
+      yimg++;
+      uimg++;
+      vimg++;
+
+    }
+  }
+}
+
+
+ImageType ImageUtils::readBinaryPPM(const char *filename, int &width, int &height)
+{
+
+  FILE *imgin = NULL;
+  int mval=0, format=0, eret;
+  ImageType ret = IMAGE_TYPE_NOIMAGE;
+
+  imgin = fopen(filename, "r");
+  if (imgin == NULL) {
+    fprintf(stderr, "Error: Filename %s not found\n", filename);
+    return ret;
+  }
+
+  eret = fscanf(imgin, "P%d\n", &format);
+  if (format != 6) {
+    fprintf(stderr, "Error: readBinaryPPM only supports PPM format (P6)\n");
+    return ret;
+  }
+
+  eret = fscanf(imgin, "%d %d\n", &width, &height);
+  eret = fscanf(imgin, "%d\n", &mval);
+  ret  = allocateImage(width, height, IMAGE_TYPE_NUM_CHANNELS);
+  eret = fread(ret, sizeof(ImageTypeBase), IMAGE_TYPE_NUM_CHANNELS*width*height, imgin);
+
+  fclose(imgin);
+
+  return ret;
+
+}
+
+void ImageUtils::writeBinaryPPM(ImageType image, const char *filename, int width, int height, int numChannels)
+{
+  FILE *imgout = fopen(filename, "w");
+
+  if (imgout == NULL) {
+    fprintf(stderr, "Error: Filename %s could not be opened for writing\n", filename);
+    return;
+  }
+
+  if (numChannels == 3) {
+    fprintf(imgout, "P6\n%d %d\n255\n", width, height);
+  } else if (numChannels == 1) {
+    fprintf(imgout, "P5\n%d %d\n255\n", width, height);
+  } else {
+    fprintf(stderr, "Error: writeBinaryPPM: Unsupported number of channels\n");
+  }
+  fwrite(image, sizeof(ImageTypeBase), numChannels*width*height, imgout);
+
+  fclose(imgout);
+
+}
+
+ImageType ImageUtils::allocateImage(int width, int height, int numChannels, short int border)
+{
+  int overallocation = 256;
+ return (ImageType) calloc(width*height*numChannels+overallocation, sizeof(ImageTypeBase));
+}
+
+
+void ImageUtils::freeImage(ImageType image)
+{
+  free(image);
+}
+
+
+// allocation of one color image used for tmp buffers, etc.
+// format of contiguous memory block:
+//    YUVInfo struct (type + BimageInfo for Y,U, and V),
+//    Y row pointers
+//    U row pointers
+//    V row pointers
+//    Y image pixels
+//    U image pixels
+//    V image pixels
+YUVinfo *YUVinfo::allocateImage(unsigned short width, unsigned short height)
+{
+    unsigned short heightUV, widthUV;
+
+    widthUV = width;
+    heightUV = height;
+
+    // figure out how much space to hold all pixels...
+    int size = ((width * height * 3) + 8);
+    unsigned char *position = 0;
+
+    // VC 8 does not like calling free on yuv->Y.ptr since it is in
+    // the middle of a block.  So rearrange the memory layout so after
+    // calling mapYUVInforToImage yuv->Y.ptr points to the begginning
+    // of the calloc'ed block.
+    YUVinfo *yuv = (YUVinfo *) calloc(sizeof(YUVinfo), 1);
+    if (yuv) {
+        yuv->Y.width  = yuv->Y.pitch = width;
+        yuv->Y.height = height;
+        yuv->Y.border = yuv->U.border = yuv->V.border = (unsigned short) 0;
+        yuv->U.width  = yuv->U.pitch = yuv->V.width = yuv->V.pitch = widthUV;
+        yuv->U.height = yuv->V.height = heightUV;
+
+        unsigned char* block = (unsigned char*) calloc(
+                sizeof(unsigned char *) * (height + heightUV + heightUV) +
+                sizeof(unsigned char) * size, 1);
+
+        position = block;
+        unsigned char **y = (unsigned char **) (block + size);
+
+        /* Initialize and assign row pointers */
+        yuv->Y.ptr = y;
+        yuv->V.ptr = &y[height];
+        yuv->U.ptr = &y[height + heightUV];
+    }
+    if (size)
+        mapYUVInfoToImage(yuv, position);
+    return yuv;
+}
+
+// wrap YUVInfo row pointers around 3 contiguous image (color component) planes.
+// position = starting pixel in image.
+void YUVinfo::mapYUVInfoToImage(YUVinfo *img, unsigned char *position)
+{
+    int i;
+    for (i = 0; i < img->Y.height; i++, position += img->Y.width)
+        img->Y.ptr[i] = position;
+    for (i = 0; i < img->V.height; i++, position += img->V.width)
+        img->V.ptr[i] = position;
+    for (i = 0; i < img->U.height; i++, position += img->U.width)
+        img->U.ptr[i] = position;
+}
+
+
diff --git a/jni_mosaic/feature_mos/src/mosaic/ImageUtils.h b/jni_mosaic/feature_mos/src/mosaic/ImageUtils.h
new file mode 100644
index 0000000..92965ca
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/ImageUtils.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// ImageUtils.h
+// $Id: ImageUtils.h,v 1.9 2011/05/16 15:33:06 mbansal Exp $
+
+#ifndef IMAGE_UTILS_H
+#define IMAGE_UTILS_H
+
+#include <stdlib.h>
+
+/**
+ *  Definition of basic image types
+ */
+typedef unsigned char ImageTypeBase;
+typedef ImageTypeBase *ImageType;
+
+typedef short ImageTypeShortBase;
+typedef ImageTypeShortBase *ImageTypeShort;
+
+typedef float ImageTypeFloatBase;
+typedef ImageTypeFloatBase *ImageTypeFloat;
+
+
+class ImageUtils {
+public:
+
+  /**
+   *  Default number of channels in image.
+   */
+  static const int IMAGE_TYPE_NUM_CHANNELS = 3;
+
+  /**
+   *  Definition of an empty image.
+   */
+  static const int IMAGE_TYPE_NOIMAGE = 0;
+
+  /**
+   *  Convert image from BGR (interlaced) to YVU (non-interlaced)
+   *
+   *  Arguments:
+   *    out: Resulting image (note must be preallocated before
+   *    call)
+   *    in: Input image
+   *    width: Width of input image
+   *    height: Height of input image
+   */
+  static void rgb2yvu(ImageType out, ImageType in, int width, int height);
+
+  static void rgba2yvu(ImageType out, ImageType in, int width, int height);
+
+  /**
+   *  Convert image from YVU (non-interlaced) to BGR (interlaced)
+   *
+   *  Arguments:
+   *    out: Resulting image (note must be preallocated before
+   *    call)
+   *    in: Input image
+   *    width: Width of input image
+   *    height: Height of input image
+   */
+  static void yvu2rgb(ImageType out, ImageType in, int width, int height);
+  static void yvu2bgr(ImageType out, ImageType in, int width, int height);
+
+  /**
+   *  Convert image from BGR to grayscale
+   *
+   *  Arguments:
+   *    in: Input image
+   *    width: Width of input image
+   *    height: Height of input image
+   *
+   *  Return:
+   *    Pointer to resulting image (allocation is done here, free
+   *    must be done by caller)
+   */
+  static ImageType rgb2gray(ImageType in, int width, int height);
+  static ImageType rgb2gray(ImageType out, ImageType in, int width, int height);
+
+  /**
+   *  Read a binary PPM image
+   */
+  static ImageType readBinaryPPM(const char *filename, int &width, int &height);
+
+  /**
+   *  Write a binary PPM image
+   */
+  static void writeBinaryPPM(ImageType image, const char *filename, int width, int height, int numChannels = IMAGE_TYPE_NUM_CHANNELS);
+
+  /**
+   *  Allocate space for a standard image.
+   */
+  static ImageType allocateImage(int width, int height, int numChannels, short int border = 0);
+
+  /**
+   *  Free memory of image
+   */
+  static void freeImage(ImageType image);
+
+  static ImageType *imageTypeToRowPointers(ImageType out, int width, int height);
+  /**
+   *  Get time.
+   */
+  static double getTime();
+
+protected:
+
+  /**
+  *  Constants for YVU/RGB conversion
+  */
+  static const int REDY = 257;
+  static const int REDV = 439;
+  static const int REDU = 148;
+  static const int GREENY = 504;
+  static const int GREENV = 368;
+  static const int GREENU = 291;
+  static const int BLUEY = 98;
+  static const int BLUEV = 71;
+  static const int BLUEU = 439;
+
+};
+
+/**
+ * Structure containing an image and other bookkeeping items.
+ * Used in YUVinfo to store separate YVU image planes.
+ */
+typedef struct {
+  ImageType *ptr;
+  unsigned short width;
+  unsigned short height;
+  unsigned short border;
+  unsigned short pitch;
+} BimageInfo;
+
+/**
+ * A YUV image container,
+ */
+class YUVinfo {
+public:
+  static YUVinfo *allocateImage(unsigned short width, unsigned short height);
+  static void mapYUVInfoToImage(YUVinfo *img, unsigned char *position);
+
+  /**
+  * Y Plane
+  */
+  BimageInfo Y;
+
+  /**
+  *  V (1st color) plane
+  */
+  BimageInfo V;
+
+  /**
+  *  U (1st color) plane
+  */
+  BimageInfo U;
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Interp.h b/jni_mosaic/feature_mos/src/mosaic/Interp.h
new file mode 100644
index 0000000..19c4a40
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Interp.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////////////
+// Interp.h
+// $Id: Interp.h,v 1.2 2011/06/17 13:35:48 mbansal Exp $
+
+#ifndef INTERP_H
+#define INTERP_H
+
+#include "Pyramid.h"
+
+#define CTAPS 40
+static double ciTable[81] = {
+        1, 0.998461, 0.993938, 0.98657, 0.9765,
+        0.963867, 0.948813, 0.931477, 0.912, 0.890523,
+        0.867188, 0.842133, 0.8155, 0.78743, 0.758062,
+        0.727539, 0.696, 0.663586, 0.630437, 0.596695,
+        0.5625, 0.527992, 0.493312, 0.458602, 0.424,
+        0.389648, 0.355687, 0.322258, 0.2895, 0.257555,
+        0.226562, 0.196664, 0.168, 0.140711, 0.114937,
+        0.0908203, 0.0685, 0.0481172, 0.0298125, 0.0137266,
+        0, -0.0118828, -0.0225625, -0.0320859, -0.0405,
+        -0.0478516, -0.0541875, -0.0595547, -0.064, -0.0675703,
+        -0.0703125, -0.0722734, -0.0735, -0.0740391, -0.0739375,
+        -0.0732422, -0.072, -0.0702578, -0.0680625, -0.0654609,
+        -0.0625, -0.0592266, -0.0556875, -0.0519297, -0.048,
+        -0.0439453, -0.0398125, -0.0356484, -0.0315, -0.0274141,
+        -0.0234375, -0.0196172, -0.016, -0.0126328, -0.0095625,
+        -0.00683594, -0.0045, -0.00260156, -0.0011875, -0.000304687, 0.0
+};
+
+inline double ciCalc(PyramidShort *img, int xi, int yi, double xfrac, double yfrac)
+{
+  double tmpf[4];
+
+  // Interpolate using 16 points
+  ImageTypeShortBase *in = img->ptr[yi-1] + xi - 1;
+  int off = (int)(xfrac * CTAPS);
+
+  tmpf[0] = in[0] * ciTable[off + 40];
+  tmpf[0] += in[1] * ciTable[off];
+  tmpf[0] += in[2] * ciTable[40 - off];
+  tmpf[0] += in[3] * ciTable[80 - off];
+  in += img->pitch;
+  tmpf[1] = in[0] * ciTable[off + 40];
+  tmpf[1] += in[1] * ciTable[off];
+  tmpf[1] += in[2] * ciTable[40 - off];
+  tmpf[1] += in[3] * ciTable[80 - off];
+  in += img->pitch;
+  tmpf[2] = in[0] * ciTable[off + 40];
+  tmpf[2] += in[1] * ciTable[off];
+  tmpf[2] += in[2] * ciTable[40 - off];
+  tmpf[2] += in[3] * ciTable[80 - off];
+  in += img->pitch;
+  tmpf[3] = in[0] * ciTable[off + 40];
+  tmpf[3] += in[1] * ciTable[off];
+  tmpf[3] += in[2] * ciTable[40 - off];
+  tmpf[3] += in[3] * ciTable[80 - off];
+
+  // this is the final interpolation
+  off = (int)(yfrac * CTAPS);
+  return (ciTable[off + 40] * tmpf[0] + ciTable[off] * tmpf[1] +
+          ciTable[40 - off] * tmpf[2] + ciTable[80 - off] * tmpf[3]);
+}
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Log.h b/jni_mosaic/feature_mos/src/mosaic/Log.h
new file mode 100644
index 0000000..cf6f14b
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Log.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+#ifndef LOG_H_
+#define LOG_H
+
+#include <android/log.h>
+#define LOGV(...) __android_log_print(ANDROID_LOG_SILENT, LOG_TAG, __VA_ARGS__)
+#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/MatrixUtils.h b/jni_mosaic/feature_mos/src/mosaic/MatrixUtils.h
new file mode 100644
index 0000000..a0b84d8
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/MatrixUtils.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Matrixutils.h
+// $Id: MatrixUtils.h,v 1.5 2011/05/16 15:33:06 mbansal Exp $
+
+
+#ifndef MATRIX_UTILS_H
+#define MATRIX_UTILS_H
+
+/* Simple class for 3x3 matrix, mainly used to convert from 9x1
+ * to 3x3
+ */
+class Matrix33 {
+public:
+
+  /**
+   *  Empty constructor
+   */
+  Matrix33() {
+    initialize();
+  }
+
+  /**
+   *  Constructor with identity initialization
+   *  Arguments:
+   *     identity: Specifies wether to initialize matrix to
+   *     identity or zeros
+   */
+  Matrix33(bool identity) {
+    initialize(identity);
+  }
+
+  /**
+   *  Initialize to identity matrix
+   */
+  void initialize(bool identity = false) {
+    mat[0][1] = mat[0][2] = mat[1][0] = mat[1][2] = mat[2][0] = mat[2][1] = 0.0;
+    if (identity) {
+      mat[0][0] = mat[1][1] = mat[2][2] = 1.0;
+    } else {
+      mat[0][0] = mat[1][1] = mat[2][2] = 0.0;
+    }
+  }
+
+  /**
+   *  Conver ta 9x1 matrix to a 3x3 matrix
+   */
+  static void convert9to33(double out[3][3], double in[9]) {
+    out[0][0] = in[0];
+    out[0][1] = in[1];
+    out[0][2] = in[2];
+
+    out[1][0] = in[3];
+    out[1][1] = in[4];
+    out[1][2] = in[5];
+
+    out[2][0] = in[6];
+    out[2][1] = in[7];
+    out[2][2] = in[8];
+
+  }
+
+  /* Matrix data */
+  double mat[3][3];
+
+};
+
+/* Simple class for 9x1 matrix, mainly used to convert from 3x3
+ * to 9x1
+ */
+class Matrix9 {
+public:
+
+  /**
+   *  Empty constructor
+   */
+  Matrix9() {
+    initialize();
+  }
+
+  /**
+   *  Constructor with identity initialization
+   *  Arguments:
+   *     identity: Specifies wether to initialize matrix to
+   *     identity or zeros
+   */
+  Matrix9(bool identity) {
+    initialize(identity);
+  }
+
+  /**
+   *  Initialize to identity matrix
+   */
+  void initialize(bool identity = false) {
+    mat[1] = mat[2] = mat[3] = mat[5] = mat[6] = mat[7] = 0.0;
+    if (identity) {
+      mat[0] = mat[4] = mat[8] = 1.0;
+    } else {
+      mat[0] = mat[4] = mat[8] = 0.0;
+    }
+  }
+
+  /**
+   *  Conver ta 3x3 matrix to a 9x1 matrix
+   */
+  static void convert33to9(double out[9], double in[3][3]) {
+    out[0] = in[0][0];
+    out[1] = in[0][1];
+    out[2] = in[0][2];
+
+    out[3] = in[1][0];
+    out[4] = in[1][1];
+    out[5] = in[1][2];
+
+    out[6] = in[2][0];
+    out[7] = in[2][1];
+    out[8] = in[2][2];
+
+  }
+
+  /* Matrix data */
+  double mat[9];
+
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Mosaic.cpp b/jni_mosaic/feature_mos/src/mosaic/Mosaic.cpp
new file mode 100644
index 0000000..7b96fa5
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Mosaic.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Mosaic.pp
+// S.O. # :
+// Author(s): zkira
+// $Id: Mosaic.cpp,v 1.20 2011/06/24 04:22:14 mbansal Exp $
+
+#include <stdio.h>
+#include <string.h>
+
+#include "Mosaic.h"
+#include "trsMatrix.h"
+
+#include "Log.h"
+#define LOG_TAG "MOSAIC"
+
+Mosaic::Mosaic()
+{
+    initialized = false;
+    imageMosaicYVU = NULL;
+    frames_size = 0;
+    max_frames = 200;
+}
+
+Mosaic::~Mosaic()
+{
+    for (int i = 0; i < frames_size; i++)
+    {
+        if (frames[i])
+            delete frames[i];
+    }
+    delete frames;
+    delete rframes;
+
+    for (int j = 0; j < owned_size; j++)
+        delete owned_frames[j];
+    delete owned_frames;
+
+    if (aligner != NULL)
+        delete aligner;
+    if (blender != NULL)
+        delete blender;
+}
+
+int Mosaic::initialize(int blendingType, int stripType, int width, int height, int nframes, bool quarter_res, float thresh_still)
+{
+    this->blendingType = blendingType;
+
+    // TODO: Review this logic if enabling FULL or PAN mode
+    if (blendingType == Blend::BLEND_TYPE_FULL ||
+            blendingType == Blend::BLEND_TYPE_PAN)
+    {
+        stripType = Blend::STRIP_TYPE_THIN;
+    }
+
+    this->stripType = stripType;
+    this->width = width;
+    this->height = height;
+
+
+    mosaicWidth = mosaicHeight = 0;
+    imageMosaicYVU = NULL;
+
+    frames = new MosaicFrame *[max_frames];
+    rframes = new MosaicFrame *[max_frames];
+
+    if(nframes>-1)
+    {
+        for(int i=0; i<nframes; i++)
+        {
+            frames[i] = new MosaicFrame(this->width,this->height,false); // Do no allocate memory for YUV data
+        }
+    }
+    else
+    {
+        for(int i=0; i<max_frames; i++)
+        {
+            frames[i] = NULL;
+        }
+    }
+
+    owned_frames = new ImageType[max_frames];
+    owned_size = 0;
+
+    LOGV("Initialize %d %d", width, height);
+    LOGV("Frame width %d,%d", width, height);
+    LOGV("Max num frames %d", max_frames);
+
+    aligner = new Align();
+    aligner->initialize(width, height,quarter_res,thresh_still);
+
+    if (blendingType == Blend::BLEND_TYPE_FULL ||
+            blendingType == Blend::BLEND_TYPE_PAN ||
+            blendingType == Blend::BLEND_TYPE_CYLPAN ||
+            blendingType == Blend::BLEND_TYPE_HORZ) {
+        blender = new Blend();
+        blender->initialize(blendingType, stripType, width, height);
+    } else {
+        blender = NULL;
+        LOGE("Error: Unknown blending type %d",blendingType);
+        return MOSAIC_RET_ERROR;
+    }
+
+    initialized = true;
+
+    return MOSAIC_RET_OK;
+}
+
+int Mosaic::addFrameRGB(ImageType imageRGB)
+{
+    ImageType imageYVU;
+    // Convert to YVU24 which is used by blending
+    imageYVU = ImageUtils::allocateImage(this->width, this->height, ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+    ImageUtils::rgb2yvu(imageYVU, imageRGB, width, height);
+
+    int existing_frames_size = frames_size;
+    int ret = addFrame(imageYVU);
+
+    if (frames_size > existing_frames_size)
+        owned_frames[owned_size++] = imageYVU;
+    else
+        ImageUtils::freeImage(imageYVU);
+
+    return ret;
+}
+
+int Mosaic::addFrame(ImageType imageYVU)
+{
+    if(frames[frames_size]==NULL)
+        frames[frames_size] = new MosaicFrame(this->width,this->height,false);
+
+    MosaicFrame *frame = frames[frames_size];
+
+    frame->image = imageYVU;
+
+    // Add frame to aligner
+    int ret = MOSAIC_RET_ERROR;
+    if (aligner != NULL)
+    {
+        // Note aligner takes in RGB images
+        int align_flag = Align::ALIGN_RET_OK;
+        align_flag = aligner->addFrame(frame->image);
+        aligner->getLastTRS(frame->trs);
+
+        if (frames_size >= max_frames)
+        {
+            LOGV("WARNING: More frames than preallocated, ignoring."
+                 "Increase maximum number of frames (-f <max_frames>) to avoid this");
+            return MOSAIC_RET_ERROR;
+        }
+
+        switch (align_flag)
+        {
+            case Align::ALIGN_RET_OK:
+                frames_size++;
+                ret = MOSAIC_RET_OK;
+                break;
+            case Align::ALIGN_RET_FEW_INLIERS:
+                frames_size++;
+                ret = MOSAIC_RET_FEW_INLIERS;
+                break;
+            case Align::ALIGN_RET_LOW_TEXTURE:
+                ret = MOSAIC_RET_LOW_TEXTURE;
+                break;
+            case Align::ALIGN_RET_ERROR:
+                ret = MOSAIC_RET_ERROR;
+                break;
+            default:
+                break;
+        }
+    }
+
+    return ret;
+}
+
+
+int Mosaic::createMosaic(float &progress, bool &cancelComputation)
+{
+    if (frames_size <= 0)
+    {
+        // Haven't accepted any frame in aligner. No need to do blending.
+        progress = TIME_PERCENT_ALIGN + TIME_PERCENT_BLEND
+                + TIME_PERCENT_FINAL;
+        return MOSAIC_RET_OK;
+    }
+
+    if (blendingType == Blend::BLEND_TYPE_PAN)
+    {
+
+        balanceRotations();
+
+    }
+
+    int ret = Blend::BLEND_RET_ERROR;
+
+    // Blend the mosaic (alignment has already been done)
+    if (blender != NULL)
+    {
+        ret = blender->runBlend((MosaicFrame **) frames, (MosaicFrame **) rframes, 
+                frames_size, imageMosaicYVU,
+                mosaicWidth, mosaicHeight, progress, cancelComputation);
+    }
+
+    switch(ret)
+    {
+        case Blend::BLEND_RET_ERROR:
+        case Blend::BLEND_RET_ERROR_MEMORY:
+            ret = MOSAIC_RET_ERROR;
+            break;
+        case Blend::BLEND_RET_CANCELLED:
+            ret = MOSAIC_RET_CANCELLED;
+            break;
+        case Blend::BLEND_RET_OK:
+            ret = MOSAIC_RET_OK;
+    }
+    return ret;
+}
+
+ImageType Mosaic::getMosaic(int &width, int &height)
+{
+    width = mosaicWidth;
+    height = mosaicHeight;
+
+    return imageMosaicYVU;
+}
+
+
+
+int Mosaic::balanceRotations()
+{
+    // Normalize to the mean angle of rotation (Smiley face)
+    double sineAngle = 0.0;
+
+    for (int i = 0; i < frames_size; i++) sineAngle += frames[i]->trs[0][1];
+    sineAngle /= frames_size;
+    // Calculate the cosineAngle (1 - sineAngle*sineAngle) = cosineAngle*cosineAngle
+    double cosineAngle = sqrt(1.0 - sineAngle*sineAngle);
+    double m[3][3] = {
+        { cosineAngle, -sineAngle, 0 },
+        { sineAngle, cosineAngle, 0},
+        { 0, 0, 1}};
+    double tmp[3][3];
+
+    for (int i = 0; i < frames_size; i++) {
+        memcpy(tmp, frames[i]->trs, sizeof(tmp));
+        mult33d(frames[i]->trs, m, tmp);
+    }
+
+    return MOSAIC_RET_OK;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic/Mosaic.h b/jni_mosaic/feature_mos/src/mosaic/Mosaic.h
new file mode 100644
index 0000000..9dea664
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Mosaic.h
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// Mosaic.h
+// S.O. # :
+// Author(s): zkira
+// $Id: Mosaic.h,v 1.16 2011/06/24 04:22:14 mbansal Exp $
+
+#ifndef MOSAIC_H
+#define MOSAIC_H
+
+#include "ImageUtils.h"
+#include "AlignFeatures.h"
+#include "Blend.h"
+#include "MosaicTypes.h"
+
+/*! \mainpage Mosaic
+
+    \section intro Introduction
+    The class Mosaic provides a simple interface to the panoramic mosaicing algorithm. The class allows passing in individual image frames to be stitched together, computes the alignment transformation between them, and then stitches and blends them together into a single panoramic output which can then be accessed as a single image. \
+
+    \section usage Usage
+    The class methods need to be called as outlined in the sample application which is created from the mosaic_main.cpp file in the directory src/mosaic/. A brief snapshot of the flow is given below:
+
+    \code
+    Mosaic mosaic;
+    // Define blending types to use, and the frame dimensions
+    int blendingType = Blend::BLEND_TYPE_CYLPAN;
+    int stripType = Blend::STRIP_TYPE_THIN;
+    int width = 640;
+    int height = 480;
+
+    while (<image frames are available>)
+    {
+        // Check for initialization and if not, initialize
+        if (!mosaic.isInitialized())
+        {
+          // Initialize mosaic processing
+          mosaic.initialize(blendingType, stripType, width, height, -1, false, 5.0f);
+        }
+
+        // Add to list of frames
+        mosaic.addFrameRGB(imageRGB);
+
+        // Free image
+        ImageUtils::freeImage(imageRGB);
+    }
+
+    // Create the mosaic
+    ret = mosaic.createMosaic();
+
+    // Get back the result
+    resultYVU = mosaic.getMosaic(mosaicWidth, mosaicHeight);
+
+    printf("Got mosaic of size %d,%d\n", mosaicWidth, mosaicHeight);
+
+    \endcode
+*/
+
+/*!
+ *  Main class that creates a mosaic by creating an aligner and blender.
+ */
+class Mosaic
+{
+
+public:
+
+  Mosaic();
+  ~Mosaic();
+
+   /*!
+    *   Creates the aligner and blender and initializes state.
+    *   \param blendingType Type of blending to perform
+    *   \param stripType    Type of strip to use. 0: thin, 1: wide. stripType
+    *                       is effective only when blendingType is CylPan or
+    *                       Horz. Otherwise, it is set to thin irrespective of the input.
+    *   \param width        Width of input images (note: all images must be same size)
+    *   \param height       Height of input images (note: all images must be same size)
+    *   \param nframes      Number of frames to pre-allocate; default value -1 will allocate each frame as it comes
+    *   \param quarter_res  Whether to compute alignment at quarter the input resolution (default = false)
+    *   \param thresh_still Minimum number of pixels of translation detected between the new frame and the last frame before this frame is added to be mosaiced. For the low-res processing at 320x180 resolution input, we set this to 5 pixels. To reject no frames, set this to 0.0 (default value).
+    *   \return             Return code signifying success or failure.
+    */
+  int initialize(int blendingType, int stripType, int width, int height, int nframes = -1, bool quarter_res = false, float thresh_still = 0.0);
+
+   /*!
+    *   Adds a YVU frame to the mosaic.
+    *   \param imageYVU     Pointer to a YVU image.
+    *   \return             Return code signifying success or failure.
+    */
+  int addFrame(ImageType imageYVU);
+
+   /*!
+    *   Adds a RGB frame to the mosaic.
+    *   \param imageRGB     Pointer to a RGB image.
+    *   \return             Return code signifying success or failure.
+    */
+  int addFrameRGB(ImageType imageRGB);
+
+   /*!
+    *   After adding all frames, call this function to perform the final blending.
+    *   \param progress     Variable to set the current progress in.
+    *   \return             Return code signifying success or failure.
+    */
+  int createMosaic(float &progress, bool &cancelComputation);
+
+    /*!
+    *   Obtains the resulting mosaic and its dimensions.
+    *   \param width        Width of the resulting mosaic (returned)
+    *   \param height       Height of the resulting mosaic (returned)
+    *   \return             Pointer to image.
+    */
+  ImageType getMosaic(int &width, int &height);
+
+    /*!
+    *   Provides access to the internal alignment object pointer.
+    *   \return             Pointer to the aligner object.
+    */
+  Align* getAligner() { return aligner; }
+
+    /*!
+    *   Obtain initialization state.
+    *
+    *   return              Returns true if initialized, false otherwise.
+    */
+  bool isInitialized() { return initialized; }
+
+
+  /*!
+   *  Return codes for mosaic.
+   */
+  static const int MOSAIC_RET_OK    = 1;
+  static const int MOSAIC_RET_ERROR = -1;
+  static const int MOSAIC_RET_CANCELLED = -2;
+  static const int MOSAIC_RET_LOW_TEXTURE = -3;
+  static const int MOSAIC_RET_FEW_INLIERS = 2;
+
+protected:
+
+  /**
+   * Size of image frames making up mosaic
+   */
+  int width, height;
+
+  /**
+   * Size of actual mosaic
+   */
+  int mosaicWidth, mosaicHeight;
+
+  /**
+   * Bounding box to crop the mosaic when the gray border is not desired.
+   */
+  MosaicRect mosaicCroppingRect;
+
+  ImageType imageMosaicYVU;
+
+  /**
+   * Collection of frames that will make up mosaic.
+   */
+  MosaicFrame **frames;
+
+  /**
+    * Subset of frames that are considered as relevant.
+    */
+  MosaicFrame **rframes;
+
+  int frames_size;
+  int max_frames;
+
+  /**
+    * Implicitly created frames, should be freed by Mosaic.
+    */
+  ImageType *owned_frames;
+  int owned_size;
+
+  /**
+   * Initialization state.
+   */
+  bool initialized;
+
+  /**
+   *  Type of blending to perform.
+   */
+  int blendingType;
+
+  /**
+    * Type of strip to use. 0: thin (default), 1: wide
+    */
+  int stripType;
+
+  /**
+   *  Pointer to aligner.
+   */
+  Align *aligner;
+
+  /**
+   *  Pointer to blender.
+   */
+  Blend *blender;
+
+  /**
+   *  Modifies TRS matrices so that rotations are balanced
+   *  about center of mosaic
+   *
+   * Side effect: TRS matrices of all mosaic frames
+   *              are modified
+   */
+  int balanceRotations();
+
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/MosaicTypes.h b/jni_mosaic/feature_mos/src/mosaic/MosaicTypes.h
new file mode 100644
index 0000000..395ec45
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/MosaicTypes.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// MosaicTypes.h
+// S.O. # :
+// Author(s): zkira
+// $Id: MosaicTypes.h,v 1.15 2011/06/17 13:35:48 mbansal Exp $
+
+
+#ifndef MOSAIC_TYPES_H
+#define MOSAIC_TYPES_H
+
+#include "ImageUtils.h"
+
+/**
+ *  Definition of rectangle in a mosaic.
+ */
+class MosaicRect
+{
+    public:
+        MosaicRect()
+        {
+            left = right = top = bottom = 0.0;
+        }
+
+        inline int Width()
+        {
+            return right - left;
+        }
+
+        inline int Height()
+        {
+            return bottom - top;
+        }
+
+        /**
+         *  Bounds of the rectangle
+         */
+        int left, right, top, bottom;
+};
+
+class BlendRect
+{
+    public:
+    double lft, rgt, top, bot;
+};
+
+/**
+ *  A frame making up the mosaic.
+ *  Note: Currently assumes a YVU image
+ *  containing separate Y,V, and U planes
+ *  in contiguous memory (in that order).
+ */
+class MosaicFrame {
+public:
+  ImageType image;
+  double trs[3][3];
+  int width, height;
+  BlendRect brect;  // This frame warped to the Mosaic coordinate system
+  BlendRect vcrect; // brect clipped using the voronoi neighbors
+  bool internal_allocation;
+
+  MosaicFrame() { };
+  MosaicFrame(int _width, int _height, bool allocate=true)
+  {
+    width = _width;
+    height = _height;
+    internal_allocation = allocate;
+    if(internal_allocation)
+        image = ImageUtils::allocateImage(width, height, ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+  }
+
+
+  ~MosaicFrame()
+  {
+    if(internal_allocation)
+        if (image)
+        free(image);
+  }
+
+  /**
+  *  Get the V plane of the image.
+  */
+  inline ImageType getV()
+  {
+    return (image + (width*height));
+  }
+
+  /**
+  *  Get the U plane of the image.
+  */
+  inline ImageType getU()
+  {
+    return (image + (width*height*2));
+  }
+
+  /**
+  *  Get a pixel from the V plane of the image.
+  */
+  inline int getV(int y, int x)
+  {
+    ImageType U = image + (width*height);
+    return U[y*width+x];
+  }
+
+  /**
+  *  Get a pixel from the U plane of the image.
+  */
+  inline int getU(int y, int x)
+  {
+    ImageType U = image + (width*height*2);
+    return U[y*width+x];
+  }
+
+};
+
+/**
+ *  Structure for describing a warp.
+ */
+typedef struct {
+  int horizontal;
+  double theta;
+  double x;
+  double y;
+  double width;
+  double radius;
+  double direction;
+  double correction;
+  int blendRange;
+  int blendRangeUV;
+  int nlevs;
+  int nlevsC;
+  int blendingType;
+  int stripType;
+  // Add an overlap to prevent a gap between pictures due to roundoffs
+  double roundoffOverlap;// 1.5
+
+} BlendParams;
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/Pyramid.cpp b/jni_mosaic/feature_mos/src/mosaic/Pyramid.cpp
new file mode 100644
index 0000000..b022d73
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Pyramid.cpp
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// pyramid.cpp
+
+#include <stdio.h>
+#include <string.h>
+
+#include "Pyramid.h"
+
+// We allocate the entire pyramid into one contiguous storage. This makes
+// cleanup easier than fragmented stuff. In addition, we added a "pitch"
+// field, so pointer manipulation is much simpler when it would be faster.
+PyramidShort *PyramidShort::allocatePyramidPacked(real levels,
+        real width, real height, real border)
+{
+    real border2 = (real) (border << 1);
+    int lines, size = calcStorage(width, height, border2, levels, &lines);
+
+    PyramidShort *img = (PyramidShort *) calloc(sizeof(PyramidShort) * levels
+            + sizeof(short *) * lines +
+            + sizeof(short) * size, 1);
+
+    if (img) {
+        PyramidShort *curr, *last;
+        ImageTypeShort *y = (ImageTypeShort *) &img[levels];
+        ImageTypeShort position = (ImageTypeShort) &y[lines];
+        for (last = (curr = img) + levels; curr < last; curr++) {
+            curr->width = width;
+            curr->height = height;
+            curr->border = border;
+            curr->pitch = (real) (width + border2);
+            curr->ptr = y + border;
+
+            // Assign row pointers
+            for (int j = height + border2; j--; y++, position += curr->pitch) {
+                *y = position + border;
+            }
+
+            width >>= 1;
+            height >>= 1;
+        }
+    }
+
+    return img;
+}
+
+// Allocate an image of type short
+PyramidShort *PyramidShort::allocateImage(real width, real height, real border)
+{
+    real border2 = (real) (border << 1);
+    PyramidShort *img = (PyramidShort *)
+        calloc(sizeof(PyramidShort) + sizeof(short *) * (height + border2) +
+                sizeof(short) * (width + border2) * (height + border2), 1);
+
+    if (img) {
+        short **y = (short **) &img[1];
+        short *position = (short *) &y[height + border2];
+        img->width = width;
+        img->height = height;
+        img->border = border;
+        img->pitch = (real) (width + border2);
+        img->ptr = y + border;
+        position += border; // Move position down to origin of real image
+
+        // Assign row pointers
+        for (int j = height + border2; j--; y++, position += img->pitch) {
+            *y = position;
+        }
+    }
+
+    return img;
+}
+
+// Free the images
+void PyramidShort::freeImage(PyramidShort *image)
+{
+    if (image != NULL)
+        free(image);
+}
+
+// Calculate amount of storage needed taking into account the borders, etc.
+unsigned int PyramidShort::calcStorage(real width, real height, real border2,   int levels, int *lines)
+{
+    int size;
+
+    *lines = size = 0;
+
+    while(levels--) {
+        size += (width + border2) * (height + border2);
+        *lines += height + border2;
+        width >>= 1;
+        height >>= 1;
+    }
+
+    return size;
+}
+
+void PyramidShort::BorderSpread(PyramidShort *pyr, int left, int right,
+        int top, int bot)
+{
+    int off, off2, height, h, w;
+    ImageTypeShort base;
+
+    if (left || right) {
+        off = pyr->border - left;
+        off2 = pyr->width + off + pyr->border - right - 1;
+        h = pyr->border - top;
+        height = pyr->height + (h << 1);
+        base = pyr->ptr[-h] - off;
+
+        // spread in X
+        for (h = height; h--; base += pyr->pitch) {
+            for (w = left; w--;)
+                base[-1 - w] = base[0];
+            for (w = right; w--;)
+                base[off2 + w + 1] = base[off2];
+        }
+    }
+
+    if (top || bot) {
+        // spread in Y
+        base = pyr->ptr[top - pyr->border] - pyr->border;
+        for (h = top; h--; base -= pyr->pitch) {
+            memcpy(base - pyr->pitch, base, pyr->pitch * sizeof(short));
+        }
+
+        base = pyr->ptr[pyr->height + pyr->border - bot] - pyr->border;
+        for (h = bot; h--; base += pyr->pitch) {
+            memcpy(base, base - pyr->pitch, pyr->pitch * sizeof(short));
+        }
+    }
+}
+
+void PyramidShort::BorderExpandOdd(PyramidShort *in, PyramidShort *out, PyramidShort *scr,
+        int mode)
+{
+    int i,j;
+    int off = in->border / 2;
+
+    // Vertical Filter
+    for (j = -off; j < in->height + off; j++) {
+        int j2 = j * 2;
+        int limit = scr->width + scr->border;
+        for (i = -scr->border; i < limit; i++) {
+            int t1 = in->ptr[j][i];
+            int t2 = in->ptr[j+1][i];
+            scr->ptr[j2][i] = (short)
+                ((6 * t1 + (in->ptr[j-1][i] + t2) + 4) >> 3);
+            scr->ptr[j2+1][i] = (short)((t1 + t2 + 1) >> 1);
+        }
+    }
+
+    BorderSpread(scr, 0, 0, 3, 3);
+
+    // Horizontal Filter
+    int limit = out->height + out->border;
+    for (j = -out->border; j < limit; j++) {
+        for (i = -off; i < scr->width + off; i++) {
+            int i2 = i * 2;
+            int t1 = scr->ptr[j][i];
+            int t2 = scr->ptr[j][i+1];
+            out->ptr[j][i2] = (short) (out->ptr[j][i2] +
+                    (mode * ((6 * t1 +
+                              scr->ptr[j][i-1] + t2 + 4) >> 3)));
+            out->ptr[j][i2+1] = (short) (out->ptr[j][i2+1] +
+                    (mode * ((t1 + t2 + 1) >> 1)));
+        }
+    }
+
+}
+
+int PyramidShort::BorderExpand(PyramidShort *pyr, int nlev, int mode)
+{
+    PyramidShort *tpyr = pyr + nlev - 1;
+    PyramidShort *scr = allocateImage(pyr[1].width, pyr[0].height, pyr->border);
+    if (scr == NULL) return 0;
+
+    if (mode > 0) {
+        // Expand and add (reconstruct from Laplacian)
+        for (; tpyr > pyr; tpyr--) {
+            scr->width = tpyr[0].width;
+            scr->height = tpyr[-1].height;
+            BorderExpandOdd(tpyr, tpyr - 1, scr, 1);
+        }
+    }
+    else if (mode < 0) {
+        // Expand and subtract (build Laplacian)
+        while ((pyr++) < tpyr) {
+            scr->width = pyr[0].width;
+            scr->height = pyr[-1].height;
+            BorderExpandOdd(pyr, pyr - 1, scr, -1);
+        }
+    }
+
+    freeImage(scr);
+    return 1;
+}
+
+void PyramidShort::BorderReduceOdd(PyramidShort *in, PyramidShort *out, PyramidShort *scr)
+{
+    ImageTypeShortBase *s, *ns, *ls, *p, *np;
+
+    int off = scr->border - 2;
+    s = scr->ptr[-scr->border] - (off >> 1);
+    ns = s + scr->pitch;
+    ls = scr->ptr[scr->height + scr->border - 1] + scr->pitch - (off >> 1);
+    int width = scr->width + scr->border;
+    p = in->ptr[-scr->border] - off;
+    np = p + in->pitch;
+
+    // treat it as if the whole thing were the image
+    for (; s < ls; s = ns, ns += scr->pitch, p = np, np += in->pitch) {
+        for (int w = width; w--; s++, p += 2) {
+            *s = (short)((((int) p[-2]) + ((int) p[2]) + 8 +    // 1
+                        ((((int) p[-1]) + ((int) p[1])) << 2) + // 4
+                        ((int) *p) * 6) >> 4);          // 6
+        }
+    }
+
+    BorderSpread(scr, 5, 4 + ((in->width ^ 1) & 1), 0, 0); //
+
+    s = out->ptr[-(off >> 1)] - out->border;
+    ns = s + out->pitch;
+    ls = s + out->pitch * (out->height + off);
+    p = scr->ptr[-off] - out->border;
+    int pitch = scr->pitch;
+    int pitch2 = pitch << 1;
+    np = p + pitch2;
+    for (; s < ls; s = ns, ns += out->pitch, p = np, np += pitch2) {
+        for (int w = out->pitch; w--; s++, p++) {
+            *s = (short)((((int) p[-pitch2]) + ((int) p[pitch2]) + 8 + // 1
+                        ((((int) p[-pitch]) + ((int) p[pitch])) << 2) + // 4
+                        ((int) *p) * 6) >> 4);              // 6
+        }
+    }
+    BorderSpread(out, 0, 0, 5, 5);
+
+}
+
+int PyramidShort::BorderReduce(PyramidShort *pyr, int nlev)
+{
+    PyramidShort *scr = allocateImage(pyr[1].width, pyr[0].height, pyr->border);
+    if (scr == NULL)
+        return 0;
+
+    BorderSpread(pyr, pyr->border, pyr->border, pyr->border, pyr->border);
+    while (--nlev) {
+        BorderReduceOdd(pyr, pyr + 1, scr);
+        pyr++;
+        scr->width = pyr[1].width;
+        scr->height = pyr[0].height;
+    }
+
+    freeImage(scr);
+    return 1;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic/Pyramid.h b/jni_mosaic/feature_mos/src/mosaic/Pyramid.h
new file mode 100644
index 0000000..c5fe907
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/Pyramid.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// Pyramid.h
+
+#ifndef PYRAMID_H
+#define PYRAMID_H
+
+#include "ImageUtils.h"
+
+typedef unsigned short int real;
+
+//  Structure containing a packed pyramid of type ImageTypeShort.  Used for pyramid
+//  blending, among other things.
+
+class PyramidShort
+{
+
+public:
+
+  ImageTypeShort *ptr;              // Pointer containing the image
+  real width, height;               // Width and height of input images
+  real numChannels;                 // Number of channels in input images
+  real border;                      // border size
+  real pitch;                       // Pitch.  Used for moving through image efficiently.
+
+  static PyramidShort *allocatePyramidPacked(real width, real height, real levels, real border = 0);
+  static PyramidShort *allocateImage(real width, real height, real border);
+  static void createPyramid(ImageType image, PyramidShort *pyramid, int last = 3 );
+  static void freeImage(PyramidShort *image);
+
+  static unsigned int calcStorage(real width, real height, real border2, int levels, int *lines);
+
+  static void BorderSpread(PyramidShort *pyr, int left, int right, int top, int bot);
+  static void BorderExpandOdd(PyramidShort *in, PyramidShort *out, PyramidShort *scr, int mode);
+  static int BorderExpand(PyramidShort *pyr, int nlev, int mode);
+  static int BorderReduce(PyramidShort *pyr, int nlev);
+  static void BorderReduceOdd(PyramidShort *in, PyramidShort *out, PyramidShort *scr);
+};
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic/trsMatrix.cpp b/jni_mosaic/feature_mos/src/mosaic/trsMatrix.cpp
new file mode 100644
index 0000000..5fc6a86
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/trsMatrix.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// trsMatrix.cpp
+// $Id: trsMatrix.cpp,v 1.9 2011/06/17 13:35:48 mbansal Exp $
+
+#include "stdio.h"
+#include <math.h>
+#include "trsMatrix.h"
+
+void mult33d(double a[3][3], double b[3][3], double c[3][3])
+{
+    a[0][0] = b[0][0]*c[0][0] + b[0][1]*c[1][0] + b[0][2]*c[2][0];
+    a[0][1] = b[0][0]*c[0][1] + b[0][1]*c[1][1] + b[0][2]*c[2][1];
+    a[0][2] = b[0][0]*c[0][2] + b[0][1]*c[1][2] + b[0][2]*c[2][2];
+    a[1][0] = b[1][0]*c[0][0] + b[1][1]*c[1][0] + b[1][2]*c[2][0];
+    a[1][1] = b[1][0]*c[0][1] + b[1][1]*c[1][1] + b[1][2]*c[2][1];
+    a[1][2] = b[1][0]*c[0][2] + b[1][1]*c[1][2] + b[1][2]*c[2][2];
+    a[2][0] = b[2][0]*c[0][0] + b[2][1]*c[1][0] + b[2][2]*c[2][0];
+    a[2][1] = b[2][0]*c[0][1] + b[2][1]*c[1][1] + b[2][2]*c[2][1];
+    a[2][2] = b[2][0]*c[0][2] + b[2][1]*c[1][2] + b[2][2]*c[2][2];
+}
+
+
+// normProjMat33d
+// m = input matrix
+// return: result if successful
+int normProjMat33d(double m[3][3])
+{
+    double m22;
+
+    if(m[2][2] == 0.0)
+        {
+        return 0;
+}
+
+    m[0][0] /= m[2][2];
+    m[0][1] /= m[2][2];
+    m[0][2] /= m[2][2];
+    m[1][0] /= m[2][2];
+    m[1][1] /= m[2][2];
+    m[1][2] /= m[2][2];
+    m[2][0] /= m[2][2];
+    m[2][1] /= m[2][2];
+    m[2][2] = 1.0;
+
+    return 1;
+}
+
+// det33d
+// m = input matrix
+// returns: determinant
+double det33d(const double m[3][3])
+{
+    double result;
+
+    result  = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]);
+    result += m[0][1] * (m[1][2] * m[2][0] - m[1][0] * m[2][2]);
+    result += m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]);
+
+    return result;
+}
+
+// inv33d
+//
+void inv33d(const double m[3][3], double out[3][3])
+{
+    double det = det33d(m);
+
+    out[0][0] = (m[1][1]*m[2][2] - m[1][2]*m[2][1]) / det;
+    out[1][0] = (m[1][2]*m[2][0] - m[1][0]*m[2][2]) / det;
+    out[2][0] = (m[1][0]*m[2][1] - m[1][1]*m[2][0]) / det;
+
+    out[0][1] = (m[0][2]*m[2][1] - m[0][1]*m[2][2]) / det;
+    out[1][1] = (m[0][0]*m[2][2] - m[0][2]*m[2][0]) / det;
+    out[2][1] = (m[0][1]*m[2][0] - m[0][0]*m[2][1]) / det;
+
+    out[0][2] = (m[0][1]*m[1][2] - m[0][2]*m[1][1]) / det;
+    out[1][2] = (m[0][2]*m[1][0] - m[0][0]*m[1][2]) / det;
+    out[2][2] = (m[0][0]*m[1][1] - m[0][1]*m[1][0]) / det;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic/trsMatrix.h b/jni_mosaic/feature_mos/src/mosaic/trsMatrix.h
new file mode 100644
index 0000000..054cc33
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic/trsMatrix.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+///////////////////////////////////////////////////
+// trsMatrix.h
+// $Id: trsMatrix.h,v 1.8 2011/06/17 13:35:48 mbansal Exp $
+
+#ifndef TRSMATRIX_H_
+#define TRSMATRIX_H_
+
+
+// Calculate the determinant of a matrix
+double det33d(const double m[3][3]);
+
+// Invert a matrix
+void inv33d(const double m[3][3], double out[3][3]);
+
+// Multiply a = b * c
+void mult33d(double a[3][3], double b[3][3], double c[3][3]);
+
+// Normalize matrix so matrix[2][2] is '1'
+int normProjMat33d(double m[3][3]);
+
+inline double ProjZ(double trs[3][3], double x, double y, double f)
+{
+    return ((trs)[2][0]*(x) + (trs)[2][1]*(y) + (trs)[2][2]*(f));
+}
+
+inline double ProjX(double trs[3][3], double x, double y, double z, double f)
+{
+    return (((trs)[0][0]*(x) + (trs)[0][1]*(y) + (trs)[0][2]*(f)) / (z));
+}
+
+inline double ProjY(double trs[3][3], double x, double y, double z, double f)
+{
+    return (((trs)[1][0]*(x) + (trs)[1][1]*(y) + (trs)[1][2]*(f)) / (z));
+}
+
+
+#endif
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.cpp b/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.cpp
new file mode 100755
index 0000000..a956f23
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.cpp
@@ -0,0 +1,98 @@
+#include "FrameBuffer.h"
+
+FrameBuffer::FrameBuffer()
+{
+    Reset();
+}
+
+FrameBuffer::~FrameBuffer() {
+}
+
+void FrameBuffer::Reset() {
+    mFrameBufferName = -1;
+    mTextureName = -1;
+    mWidth = 0;
+    mHeight = 0;
+    mFormat = -1;
+}
+
+bool FrameBuffer::InitializeGLContext() {
+    Reset();
+    return CreateBuffers();
+}
+
+bool FrameBuffer::Init(int width, int height, GLenum format) {
+    if (mFrameBufferName == (GLuint)-1) {
+        if (!CreateBuffers()) {
+            return false;
+        }
+    }
+    glBindFramebuffer(GL_FRAMEBUFFER, mFrameBufferName);
+    glBindTexture(GL_TEXTURE_2D, mTextureName);
+
+    glTexImage2D(GL_TEXTURE_2D,
+                 0,
+                 format,
+                 width,
+                 height,
+                 0,
+                 format,
+                 GL_UNSIGNED_BYTE,
+                 NULL);
+    if (!checkGlError("bind/teximage")) {
+        return false;
+    }
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    // This is necessary to work with user-generated frame buffers with
+    // dimensions that are NOT powers of 2.
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    // Attach texture to frame buffer.
+    glFramebufferTexture2D(GL_FRAMEBUFFER,
+                           GL_COLOR_ATTACHMENT0,
+                           GL_TEXTURE_2D,
+                           mTextureName,
+                           0);
+    checkFramebufferStatus("FrameBuffer.cpp");
+    checkGlError("framebuffertexture2d");
+
+    if (!checkGlError("texture setup")) {
+        return false;
+    }
+    mWidth = width;
+    mHeight = height;
+    mFormat = format;
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+    return true;
+}
+
+bool FrameBuffer::CreateBuffers() {
+    glGenFramebuffers(1, &mFrameBufferName);
+    glGenTextures(1, &mTextureName);
+    if (!checkGlError("texture generation")) {
+        return false;
+    }
+    return true;
+}
+
+GLuint FrameBuffer::GetTextureName() const {
+    return mTextureName;
+}
+
+GLuint FrameBuffer::GetFrameBufferName() const {
+    return mFrameBufferName;
+}
+
+GLenum FrameBuffer::GetFormat() const {
+    return mFormat;
+}
+
+int FrameBuffer::GetWidth() const {
+    return mWidth;
+}
+
+int FrameBuffer::GetHeight() const {
+    return mHeight;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.h b/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.h
new file mode 100755
index 0000000..314b126
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/FrameBuffer.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#define checkGlError(op)  checkGLErrorDetail(__FILE__, __LINE__, (op))
+
+extern bool checkGLErrorDetail(const char* file, int line, const char* op);
+extern void checkFramebufferStatus(const char* name);
+
+class FrameBuffer {
+  public:
+    FrameBuffer();
+    virtual ~FrameBuffer();
+
+    bool InitializeGLContext();
+    bool Init(int width, int height, GLenum format);
+    GLuint GetTextureName() const;
+    GLuint GetFrameBufferName() const;
+    GLenum GetFormat() const;
+
+    int GetWidth() const;
+    int GetHeight() const;
+
+ private:
+    void Reset();
+    bool CreateBuffers();
+    GLuint mFrameBufferName;
+    GLuint mTextureName;
+    int mWidth;
+    int mHeight;
+    GLenum mFormat;
+};
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.cpp b/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.cpp
new file mode 100755
index 0000000..b9938eb
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "Renderer.h"
+
+#include "mosaic/Log.h"
+#define LOG_TAG "Renderer"
+
+#include <GLES2/gl2ext.h>
+
+Renderer::Renderer()
+      : mGlProgram(0),
+        mInputTextureName(-1),
+        mInputTextureWidth(0),
+        mInputTextureHeight(0),
+        mSurfaceWidth(0),
+        mSurfaceHeight(0)
+{
+    InitializeGLContext();
+}
+
+Renderer::~Renderer() {
+}
+
+GLuint Renderer::loadShader(GLenum shaderType, const char* pSource) {
+    GLuint shader = glCreateShader(shaderType);
+    if (shader) {
+        glShaderSource(shader, 1, &pSource, NULL);
+        glCompileShader(shader);
+        GLint compiled = 0;
+        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
+        if (!compiled) {
+            GLint infoLen = 0;
+            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
+            if (infoLen) {
+                char* buf = (char*) malloc(infoLen);
+                if (buf) {
+                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
+                    LOGE("Could not compile shader %d:\n%s\n",
+                            shaderType, buf);
+                    free(buf);
+                }
+                glDeleteShader(shader);
+                shader = 0;
+            }
+        }
+    }
+    return shader;
+}
+
+GLuint Renderer::createProgram(const char* pVertexSource, const char* pFragmentSource)
+{
+    GLuint vertexShader = loadShader(GL_VERTEX_SHADER, pVertexSource);
+    if (!vertexShader)
+    {
+        return 0;
+    }
+
+    GLuint pixelShader = loadShader(GL_FRAGMENT_SHADER, pFragmentSource);
+    if (!pixelShader)
+    {
+        return 0;
+    }
+
+    GLuint program = glCreateProgram();
+    if (program)
+    {
+        glAttachShader(program, vertexShader);
+        checkGlError("glAttachShader");
+        glAttachShader(program, pixelShader);
+        checkGlError("glAttachShader");
+
+        glLinkProgram(program);
+        GLint linkStatus = GL_FALSE;
+        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+
+        LOGI("Program Linked (%d)!", program);
+
+        if (linkStatus != GL_TRUE)
+        {
+            GLint bufLength = 0;
+            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
+            if (bufLength)
+            {
+                char* buf = (char*) malloc(bufLength);
+                if (buf)
+                {
+                    glGetProgramInfoLog(program, bufLength, NULL, buf);
+                    LOGE("Could not link program:\n%s\n", buf);
+                    free(buf);
+                }
+            }
+            glDeleteProgram(program);
+            program = 0;
+        }
+    }
+    return program;
+}
+
+// Set this renderer to use the default frame-buffer (screen) and
+// set the viewport size to be the given width and height (pixels).
+bool Renderer::SetupGraphics(int width, int height)
+{
+    bool succeeded = false;
+    do {
+        if (mGlProgram == 0)
+        {
+            if (!InitializeGLProgram())
+            {
+              break;
+            }
+        }
+        glUseProgram(mGlProgram);
+        if (!checkGlError("glUseProgram")) break;
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+        mFrameBuffer = NULL;
+        mSurfaceWidth = width;
+        mSurfaceHeight = height;
+
+        glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
+        if (!checkGlError("glViewport")) break;
+        succeeded = true;
+    } while (false);
+
+    return succeeded;
+}
+
+
+// Set this renderer to use the specified FBO and
+// set the viewport size to be the width and height of this FBO.
+bool Renderer::SetupGraphics(FrameBuffer* buffer)
+{
+    bool succeeded = false;
+    do {
+        if (mGlProgram == 0)
+        {
+            if (!InitializeGLProgram())
+            {
+              break;
+            }
+        }
+        glUseProgram(mGlProgram);
+        if (!checkGlError("glUseProgram")) break;
+
+        glBindFramebuffer(GL_FRAMEBUFFER, buffer->GetFrameBufferName());
+
+        mFrameBuffer = buffer;
+        mSurfaceWidth = mFrameBuffer->GetWidth();
+        mSurfaceHeight = mFrameBuffer->GetHeight();
+
+        glViewport(0, 0, mSurfaceWidth, mSurfaceHeight);
+        if (!checkGlError("glViewport")) break;
+        succeeded = true;
+    } while (false);
+
+    return succeeded;
+}
+
+bool Renderer::Clear(float r, float g, float b, float a)
+{
+    bool succeeded = false;
+    do {
+        bool rt = (mFrameBuffer == NULL)?
+                SetupGraphics(mSurfaceWidth, mSurfaceHeight) :
+                SetupGraphics(mFrameBuffer);
+
+        if(!rt)
+            break;
+
+        glClearColor(r, g, b, a);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        succeeded = true;
+    } while (false);
+    return succeeded;
+
+}
+
+void Renderer::InitializeGLContext()
+{
+    if(mFrameBuffer != NULL)
+    {
+        delete mFrameBuffer;
+        mFrameBuffer = NULL;
+    }
+
+    mInputTextureName = -1;
+    mInputTextureType = GL_TEXTURE_2D;
+    mGlProgram = 0;
+}
+
+int Renderer::GetTextureName()
+{
+    return mInputTextureName;
+}
+
+void Renderer::SetInputTextureName(GLuint textureName)
+{
+    mInputTextureName = textureName;
+}
+
+void Renderer::SetInputTextureType(GLenum textureType)
+{
+    mInputTextureType = textureType;
+}
+
+void Renderer::SetInputTextureDimensions(int width, int height)
+{
+    mInputTextureWidth = width;
+    mInputTextureHeight = height;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.h b/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.h
new file mode 100755
index 0000000..a43e802
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/Renderer.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "FrameBuffer.h"
+
+#include <GLES2/gl2.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+class Renderer {
+  public:
+    Renderer();
+    virtual ~Renderer();
+
+    // Initialize OpenGL resources
+    // @return true if successful
+    virtual bool InitializeGLProgram() = 0;
+
+    bool SetupGraphics(FrameBuffer* buffer);
+    bool SetupGraphics(int width, int height);
+
+    bool Clear(float r, float g, float b, float a);
+
+    int GetTextureName();
+    void SetInputTextureName(GLuint textureName);
+    void SetInputTextureDimensions(int width, int height);
+    void SetInputTextureType(GLenum textureType);
+
+    void InitializeGLContext();
+
+  protected:
+
+    GLuint loadShader(GLenum shaderType, const char* pSource);
+    GLuint createProgram(const char*, const char* );
+
+    int SurfaceWidth() const { return mSurfaceWidth; }
+    int SurfaceHeight() const { return mSurfaceHeight; }
+
+    // Source code for shaders.
+    virtual const char* VertexShaderSource() const = 0;
+    virtual const char* FragmentShaderSource() const = 0;
+
+    // Redefine this to use special texture types such as
+    // GL_TEXTURE_EXTERNAL_OES.
+    GLenum InputTextureType() const { return mInputTextureType; }
+
+    GLuint mGlProgram;
+    GLuint mInputTextureName;
+    GLenum mInputTextureType;
+    int mInputTextureWidth;
+    int mInputTextureHeight;
+
+    // Attribute locations
+    GLint  mScalingtransLoc;
+    GLint maPositionHandle;
+    GLint maTextureHandle;
+
+
+    int mSurfaceWidth;      // Width of target surface.
+    int mSurfaceHeight;     // Height of target surface.
+
+    FrameBuffer *mFrameBuffer;
+};
+
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp b/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp
new file mode 100755
index 0000000..88aac36
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "SurfaceTextureRenderer.h"
+
+#include <GLES2/gl2ext.h>
+const GLfloat g_vVertices[] = {
+    -1.f, -1.f, 0.0f, 1.0f,  // Position 0
+    0.0f,  0.0f,         // TexCoord 0
+     1.f, -1.f, 0.0f, 1.0f, // Position 1
+    1.0f,  0.0f,         // TexCoord 1
+    -1.f,  1.f, 0.0f, 1.0f, // Position 2
+    0.0f,  1.0f,         // TexCoord 2
+    1.f,   1.f, 0.0f, 1.0f, // Position 3
+    1.0f,  1.0f          // TexCoord 3
+};
+GLushort g_iIndices2[] = { 0, 1, 2, 3 };
+
+const int GL_TEXTURE_EXTERNAL_OES_ENUM = 0x8D65;
+
+const int VERTEX_STRIDE = 6 * sizeof(GLfloat);
+
+SurfaceTextureRenderer::SurfaceTextureRenderer() : Renderer() {
+    memset(mSTMatrix, 0.0, 16*sizeof(float));
+    mSTMatrix[0] = 1.0f;
+    mSTMatrix[5] = 1.0f;
+    mSTMatrix[10] = 1.0f;
+    mSTMatrix[15] = 1.0f;
+}
+
+SurfaceTextureRenderer::~SurfaceTextureRenderer() {
+}
+
+void SurfaceTextureRenderer::SetViewportMatrix(int w, int h, int W, int H)
+{
+    for(int i=0; i<16; i++)
+    {
+        mViewportMatrix[i] = 0.0f;
+    }
+
+    mViewportMatrix[0] = float(w)/float(W);
+    mViewportMatrix[5] = float(h)/float(H);
+    mViewportMatrix[10] = 1.0f;
+    mViewportMatrix[12] = -1.0f + float(w)/float(W);
+    mViewportMatrix[13] = -1.0f + float(h)/float(H);
+    mViewportMatrix[15] = 1.0f;
+}
+
+void SurfaceTextureRenderer::SetScalingMatrix(float xscale, float yscale)
+{
+    for(int i=0; i<16; i++)
+    {
+        mScalingMatrix[i] = 0.0f;
+    }
+
+    mScalingMatrix[0] = xscale;
+    mScalingMatrix[5] = yscale;
+    mScalingMatrix[10] = 1.0f;
+    mScalingMatrix[15] = 1.0f;
+}
+
+void SurfaceTextureRenderer::SetSTMatrix(float *stmat)
+{
+    memcpy(mSTMatrix, stmat, 16*sizeof(float));
+}
+
+
+bool SurfaceTextureRenderer::InitializeGLProgram()
+{
+    bool succeeded = false;
+    do {
+        GLuint glProgram;
+        glProgram = createProgram(VertexShaderSource(),
+                FragmentShaderSource());
+        if (!glProgram) {
+            break;
+        }
+
+        glUseProgram(glProgram);
+        if (!checkGlError("glUseProgram")) break;
+
+        maPositionHandle = glGetAttribLocation(glProgram, "aPosition");
+        checkGlError("glGetAttribLocation aPosition");
+        maTextureHandle = glGetAttribLocation(glProgram, "aTextureCoord");
+        checkGlError("glGetAttribLocation aTextureCoord");
+        muSTMatrixHandle = glGetUniformLocation(glProgram, "uSTMatrix");
+        checkGlError("glGetUniformLocation uSTMatrix");
+        mScalingtransLoc = glGetUniformLocation(glProgram, "u_scalingtrans");
+
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        mGlProgram = glProgram;
+        succeeded = true;
+    } while (false);
+
+    if (!succeeded && (mGlProgram != 0))
+    {
+        glDeleteProgram(mGlProgram);
+        checkGlError("glDeleteProgram");
+        mGlProgram = 0;
+    }
+    return succeeded;
+}
+
+bool SurfaceTextureRenderer::DrawTexture(GLfloat *affine)
+{
+    bool succeeded = false;
+    do {
+        bool rt = (mFrameBuffer == NULL)?
+            SetupGraphics(mSurfaceWidth, mSurfaceHeight) :
+            SetupGraphics(mFrameBuffer);
+
+        if(!rt)
+            break;
+
+        glDisable(GL_BLEND);
+
+        glActiveTexture(GL_TEXTURE0);
+        if (!checkGlError("glActiveTexture")) break;
+
+        const GLenum texture_type = InputTextureType();
+        glBindTexture(texture_type, mInputTextureName);
+        if (!checkGlError("glBindTexture")) break;
+
+        glUniformMatrix4fv(mScalingtransLoc, 1, GL_FALSE, mScalingMatrix);
+        glUniformMatrix4fv(muSTMatrixHandle, 1, GL_FALSE, mSTMatrix);
+
+        // Load the vertex position
+        glVertexAttribPointer(maPositionHandle, 4, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, g_vVertices);
+        glEnableVertexAttribArray(maPositionHandle);
+        // Load the texture coordinate
+        glVertexAttribPointer(maTextureHandle, 2, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, &g_vVertices[4]);
+        glEnableVertexAttribArray(maTextureHandle);
+
+        // And, finally, execute the GL draw command.
+        glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, g_iIndices2);
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        succeeded = true;
+    } while (false);
+    return succeeded;
+}
+
+const char* SurfaceTextureRenderer::VertexShaderSource() const
+{
+    static const char gVertexShader[] =
+        "uniform mat4 uSTMatrix;\n"
+        "uniform mat4 u_scalingtrans;  \n"
+        "attribute vec4 aPosition;\n"
+        "attribute vec4 aTextureCoord;\n"
+        "varying vec2 vTextureNormCoord;\n"
+        "void main() {\n"
+        "  gl_Position = u_scalingtrans * aPosition;\n"
+        "  vTextureNormCoord = (uSTMatrix * aTextureCoord).xy;\n"
+        "}\n";
+
+    return gVertexShader;
+}
+
+const char* SurfaceTextureRenderer::FragmentShaderSource() const
+{
+    static const char gFragmentShader[] =
+        "#extension GL_OES_EGL_image_external : require\n"
+        "precision mediump float;\n"
+        "varying vec2 vTextureNormCoord;\n"
+        "uniform samplerExternalOES sTexture;\n"
+        "void main() {\n"
+        "  gl_FragColor = texture2D(sTexture, vTextureNormCoord);\n"
+        "}\n";
+
+    return gFragmentShader;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h b/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h
new file mode 100755
index 0000000..ea2b81a
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/SurfaceTextureRenderer.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "FrameBuffer.h"
+#include "Renderer.h"
+
+#include <GLES2/gl2.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+class SurfaceTextureRenderer: public Renderer {
+  public:
+    SurfaceTextureRenderer();
+    virtual ~SurfaceTextureRenderer();
+
+    // Initialize OpenGL resources
+    // @return true if successful
+    bool InitializeGLProgram();
+
+    bool DrawTexture(GLfloat *affine);
+
+    void SetViewportMatrix(int w, int h, int W, int H);
+    void SetScalingMatrix(float xscale, float yscale);
+    void SetSTMatrix(float *stmat);
+
+ private:
+    // Source code for shaders.
+    const char* VertexShaderSource() const;
+    const char* FragmentShaderSource() const;
+
+    // Attribute locations
+    GLint  mScalingtransLoc;
+    GLint muSTMatrixHandle;
+    GLint maPositionHandle;
+    GLint maTextureHandle;
+
+    GLfloat mViewportMatrix[16];
+    GLfloat mScalingMatrix[16];
+
+    GLfloat mSTMatrix[16];
+
+};
+
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.cpp b/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.cpp
new file mode 100755
index 0000000..af6779a
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "WarpRenderer.h"
+
+#include <GLES2/gl2ext.h>
+
+const GLfloat g_vVertices[] = {
+    -1.f, 1.f, 0.0f, 1.0f,  // Position 0
+    0.0f,  1.0f,         // TexCoord 0
+     1.f, 1.f, 0.0f, 1.0f, // Position 1
+    1.0f,  1.0f,         // TexCoord 1
+    -1.f, -1.f, 0.0f, 1.0f, // Position 2
+    0.0f,  0.0f,         // TexCoord 2
+    1.f,  -1.f, 0.0f, 1.0f, // Position 3
+    1.0f,  0.0f          // TexCoord 3
+};
+
+const int VERTEX_STRIDE = 6 * sizeof(GLfloat);
+
+GLushort g_iIndices[] = { 0, 1, 2, 3 };
+
+WarpRenderer::WarpRenderer() : Renderer()
+{
+}
+
+WarpRenderer::~WarpRenderer() {
+}
+
+void WarpRenderer::SetViewportMatrix(int w, int h, int W, int H)
+{
+    for(int i=0; i<16; i++)
+    {
+        mViewportMatrix[i] = 0.0f;
+    }
+
+    mViewportMatrix[0] = float(w)/float(W);
+    mViewportMatrix[5] = float(h)/float(H);
+    mViewportMatrix[10] = 1.0f;
+    mViewportMatrix[12] = -1.0f + float(w)/float(W);
+    mViewportMatrix[13] = -1.0f + float(h)/float(H);
+    mViewportMatrix[15] = 1.0f;
+}
+
+void WarpRenderer::SetScalingMatrix(float xscale, float yscale)
+{
+    for(int i=0; i<16; i++)
+    {
+        mScalingMatrix[i] = 0.0f;
+    }
+
+    mScalingMatrix[0] = xscale;
+    mScalingMatrix[5] = yscale;
+    mScalingMatrix[10] = 1.0f;
+    mScalingMatrix[15] = 1.0f;
+}
+
+bool WarpRenderer::InitializeGLProgram()
+{
+    bool succeeded = false;
+    do {
+        GLuint glProgram;
+        glProgram = createProgram(VertexShaderSource(),
+                FragmentShaderSource());
+        if (!glProgram) {
+            break;
+        }
+
+        glUseProgram(glProgram);
+        if (!checkGlError("glUseProgram")) break;
+
+        // Get attribute locations
+        mPositionLoc     = glGetAttribLocation(glProgram, "a_position");
+        mAffinetransLoc  = glGetUniformLocation(glProgram, "u_affinetrans");
+        mViewporttransLoc = glGetUniformLocation(glProgram, "u_viewporttrans");
+        mScalingtransLoc = glGetUniformLocation(glProgram, "u_scalingtrans");
+        mTexCoordLoc     = glGetAttribLocation(glProgram, "a_texCoord");
+
+        // Get sampler location
+        mSamplerLoc      = glGetUniformLocation(glProgram, "s_texture");
+
+        mGlProgram = glProgram;
+        succeeded = true;
+    } while (false);
+
+    if (!succeeded && (mGlProgram != 0))
+    {
+        glDeleteProgram(mGlProgram);
+        checkGlError("glDeleteProgram");
+        mGlProgram = 0;
+    }
+    return succeeded;
+}
+
+bool WarpRenderer::DrawTexture(GLfloat *affine)
+{
+    bool succeeded = false;
+    do {
+        bool rt = (mFrameBuffer == NULL)?
+                SetupGraphics(mSurfaceWidth, mSurfaceHeight) :
+                SetupGraphics(mFrameBuffer);
+
+        if(!rt)
+            break;
+
+        glDisable(GL_BLEND);
+
+        glActiveTexture(GL_TEXTURE0);
+        if (!checkGlError("glActiveTexture")) break;
+
+        const GLenum texture_type = InputTextureType();
+        glBindTexture(texture_type, mInputTextureName);
+        if (!checkGlError("glBindTexture")) break;
+
+        // Set the sampler texture unit to 0
+        glUniform1i(mSamplerLoc, 0);
+
+        // Load the vertex position
+        glVertexAttribPointer(mPositionLoc, 4, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, g_vVertices);
+
+        // Load the texture coordinate
+        glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, &g_vVertices[4]);
+
+        glEnableVertexAttribArray(mPositionLoc);
+        glEnableVertexAttribArray(mTexCoordLoc);
+
+        // pass matrix information to the vertex shader
+        glUniformMatrix4fv(mAffinetransLoc, 1, GL_FALSE, affine);
+        glUniformMatrix4fv(mViewporttransLoc, 1, GL_FALSE, mViewportMatrix);
+        glUniformMatrix4fv(mScalingtransLoc, 1, GL_FALSE, mScalingMatrix);
+
+        // And, finally, execute the GL draw command.
+        glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, g_iIndices);
+
+        checkGlError("glDrawElements");
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        succeeded = true;
+    } while (false);
+    return succeeded;
+}
+
+const char* WarpRenderer::VertexShaderSource() const
+{
+    static const char gVertexShader[] =
+        "uniform mat4 u_affinetrans;  \n"
+        "uniform mat4 u_viewporttrans;  \n"
+        "uniform mat4 u_scalingtrans;  \n"
+        "attribute vec4 a_position;   \n"
+        "attribute vec2 a_texCoord;   \n"
+        "varying vec2 v_texCoord;     \n"
+        "void main()                  \n"
+        "{                            \n"
+        "   gl_Position = u_scalingtrans * u_viewporttrans * u_affinetrans * a_position; \n"
+        "   v_texCoord = a_texCoord;  \n"
+        "}                            \n";
+
+    return gVertexShader;
+}
+
+const char* WarpRenderer::FragmentShaderSource() const
+{
+    static const char gFragmentShader[] =
+        "precision mediump float;                            \n"
+        "varying vec2 v_texCoord;                            \n"
+        "uniform sampler2D s_texture;                        \n"
+        "void main()                                         \n"
+        "{                                                   \n"
+        "  vec4 color;                                       \n"
+        "  color = texture2D(s_texture, v_texCoord);       \n"
+        "  gl_FragColor = color;                             \n"
+        "}                                                   \n";
+
+    return gFragmentShader;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.h b/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.h
new file mode 100755
index 0000000..8e9a694
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/WarpRenderer.h
@@ -0,0 +1,48 @@
+#pragma once
+
+#include "FrameBuffer.h"
+#include "Renderer.h"
+
+#include <GLES2/gl2.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+class WarpRenderer: public Renderer {
+  public:
+    WarpRenderer();
+    virtual ~WarpRenderer();
+
+    // Initialize OpenGL resources
+    // @return true if successful
+    bool InitializeGLProgram();
+
+    void SetViewportMatrix(int w, int h, int W, int H);
+    void SetScalingMatrix(float xscale, float yscale);
+
+    bool DrawTexture(GLfloat *affine);
+
+ private:
+    // Source code for shaders.
+    const char* VertexShaderSource() const;
+    const char* FragmentShaderSource() const;
+
+    GLuint mTexHandle;                  // Handle to s_texture.
+    GLuint mTexCoordHandle;             // Handle to a_texCoord.
+    GLuint mTriangleVerticesHandle;     // Handle to vPosition.
+
+    // Attribute locations
+    GLint  mPositionLoc;
+    GLint  mAffinetransLoc;
+    GLint  mViewporttransLoc;
+    GLint  mScalingtransLoc;
+    GLint  mTexCoordLoc;
+
+    GLfloat mViewportMatrix[16];
+    GLfloat mScalingMatrix[16];
+
+    // Sampler location
+    GLint mSamplerLoc;
+};
+
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.cpp b/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.cpp
new file mode 100755
index 0000000..f7dcf6f
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "YVURenderer.h"
+
+#include <GLES2/gl2ext.h>
+
+const GLfloat g_vVertices[] = {
+    -1.f, 1.f, 0.0f, 1.0f,  // Position 0
+    0.0f,  1.0f,         // TexCoord 0
+     1.f, 1.f, 0.0f, 1.0f, // Position 1
+    1.0f,  1.0f,         // TexCoord 1
+    -1.f, -1.f, 0.0f, 1.0f, // Position 2
+    0.0f,  0.0f,         // TexCoord 2
+    1.f,  -1.f, 0.0f, 1.0f, // Position 3
+    1.0f,  0.0f          // TexCoord 3
+};
+
+const int VERTEX_STRIDE = 6 * sizeof(GLfloat);
+
+GLushort g_iIndices3[] = { 0, 1, 2, 3 };
+
+YVURenderer::YVURenderer() : Renderer()
+                   {
+}
+
+YVURenderer::~YVURenderer() {
+}
+
+bool YVURenderer::InitializeGLProgram()
+{
+    bool succeeded = false;
+    do {
+        GLuint glProgram;
+        glProgram = createProgram(VertexShaderSource(),
+                FragmentShaderSource());
+        if (!glProgram) {
+            break;
+        }
+
+        glUseProgram(glProgram);
+        if (!checkGlError("glUseProgram")) break;
+
+        // Get attribute locations
+        mPositionLoc     = glGetAttribLocation(glProgram, "a_Position");
+        mTexCoordLoc     = glGetAttribLocation(glProgram, "a_texCoord");
+
+        // Get sampler location
+        mSamplerLoc      = glGetUniformLocation(glProgram, "s_texture");
+
+        mGlProgram = glProgram;
+        succeeded = true;
+    } while (false);
+
+    if (!succeeded && (mGlProgram != 0))
+    {
+        glDeleteProgram(mGlProgram);
+        checkGlError("glDeleteProgram");
+        mGlProgram = 0;
+    }
+    return succeeded;
+}
+
+bool YVURenderer::DrawTexture()
+{
+    bool succeeded = false;
+    do {
+        bool rt = (mFrameBuffer == NULL)?
+                SetupGraphics(mSurfaceWidth, mSurfaceHeight) :
+                SetupGraphics(mFrameBuffer);
+
+        if(!rt)
+            break;
+
+        glDisable(GL_BLEND);
+
+        glActiveTexture(GL_TEXTURE0);
+        if (!checkGlError("glActiveTexture")) break;
+
+        const GLenum texture_type = InputTextureType();
+        glBindTexture(texture_type, mInputTextureName);
+        if (!checkGlError("glBindTexture")) break;
+
+        // Set the sampler texture unit to 0
+        glUniform1i(mSamplerLoc, 0);
+
+        // Load the vertex position
+        glVertexAttribPointer(mPositionLoc, 4, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, g_vVertices);
+
+        // Load the texture coordinate
+        glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT,
+                GL_FALSE, VERTEX_STRIDE, &g_vVertices[4]);
+
+        glEnableVertexAttribArray(mPositionLoc);
+        glEnableVertexAttribArray(mTexCoordLoc);
+
+        // And, finally, execute the GL draw command.
+        glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, g_iIndices3);
+
+        checkGlError("glDrawElements");
+
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        succeeded = true;
+    } while (false);
+    return succeeded;
+}
+
+const char* YVURenderer::VertexShaderSource() const
+{
+    // All this really does is copy the coordinates into
+    // variables for the fragment shader to pick up.
+    static const char gVertexShader[] =
+        "attribute vec4 a_Position;\n"
+        "attribute vec2 a_texCoord;\n"
+        "varying vec2 v_texCoord;\n"
+        "void main() {\n"
+        "  gl_Position = a_Position;\n"
+        "  v_texCoord = a_texCoord;\n"
+        "}\n";
+
+    return gVertexShader;
+}
+
+const char* YVURenderer::FragmentShaderSource() const
+{
+    static const char gFragmentShader[] =
+        "precision mediump float;\n"
+        "uniform sampler2D s_texture;\n"
+        "const vec4 coeff_y = vec4(0.257, 0.594, 0.098, 0.063);\n"
+        "const vec4 coeff_v = vec4(0.439, -0.368, -0.071, 0.500);\n"
+        "const vec4 coeff_u = vec4(-0.148, -0.291, 0.439, 0.500);\n"
+        "varying vec2 v_texCoord;\n"
+        "void main() {\n"
+        "  vec4 p;\n"
+        "  p = texture2D(s_texture, v_texCoord);\n"
+        "  gl_FragColor[0] = dot(p, coeff_y);\n"
+        "  p = texture2D(s_texture, v_texCoord);\n"
+        "  gl_FragColor[1] = dot(p, coeff_v);\n"
+        "  p = texture2D(s_texture, v_texCoord);\n"
+        "  gl_FragColor[2] = dot(p, coeff_u);\n"
+        "  p = texture2D(s_texture, v_texCoord);\n"
+        "  gl_FragColor[3] = dot(p, coeff_y);\n"
+        "}\n";
+
+    return gFragmentShader;
+}
diff --git a/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.h b/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.h
new file mode 100755
index 0000000..d14a4b9
--- /dev/null
+++ b/jni_mosaic/feature_mos/src/mosaic_renderer/YVURenderer.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "FrameBuffer.h"
+#include "Renderer.h"
+
+#include <GLES2/gl2.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+class YVURenderer: public Renderer {
+  public:
+    YVURenderer();
+    virtual ~YVURenderer();
+
+    // Initialize OpenGL resources
+    // @return true if successful
+    bool InitializeGLProgram();
+
+    bool DrawTexture();
+
+ private:
+    // Source code for shaders.
+    const char* VertexShaderSource() const;
+    const char* FragmentShaderSource() const;
+
+    // Attribute locations
+    GLint  mPositionLoc;
+    GLint  mTexCoordLoc;
+
+    // Sampler location
+    GLint mSamplerLoc;
+};
+
diff --git a/jni_mosaic/feature_mos_jni.cpp b/jni_mosaic/feature_mos_jni.cpp
new file mode 100644
index 0000000..0fa792a
--- /dev/null
+++ b/jni_mosaic/feature_mos_jni.cpp
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+*
+ */
+#include <string.h>
+#include <jni.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <db_utilities_camera.h>
+
+#include "mosaic/AlignFeatures.h"
+#include "mosaic/Blend.h"
+#include "mosaic/Mosaic.h"
+#include "mosaic/Log.h"
+#define LOG_TAG "FEATURE_MOS_JNI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "mosaic_renderer_jni.h"
+
+char buffer[1024];
+
+const int MAX_FRAMES = 100;
+
+static double mTx;
+
+int tWidth[NR];
+int tHeight[NR];
+
+ImageType tImage[NR][MAX_FRAMES];// = {{ImageUtils::IMAGE_TYPE_NOIMAGE}}; // YVU24 format image
+Mosaic *mosaic[NR] = {NULL,NULL};
+ImageType resultYVU = ImageUtils::IMAGE_TYPE_NOIMAGE;
+ImageType resultBGR = ImageUtils::IMAGE_TYPE_NOIMAGE;
+float gTRS[11]; // 9 elements of the transformation, 1 for frame-number, 1 for alignment error code.
+// Variables to keep track of the mosaic computation progress for both LR & HR.
+float gProgress[NR];
+// Variables to be able to cancel the mosaic computation when the GUI says so.
+bool gCancelComputation[NR];
+
+int c;
+int width=0, height=0;
+int mosaicWidth=0, mosaicHeight=0;
+
+//int blendingType = Blend::BLEND_TYPE_FULL;
+//int blendingType = Blend::BLEND_TYPE_CYLPAN;
+int blendingType = Blend::BLEND_TYPE_HORZ;
+int stripType = Blend::STRIP_TYPE_THIN;
+bool high_res = false;
+bool quarter_res[NR] = {false,false};
+float thresh_still[NR] = {5.0f,0.0f};
+
+/* return current time in milliseconds*/
+
+#ifndef now_ms
+static double
+now_ms(void)
+{
+    //struct timespec res;
+    struct timeval res;
+    //clock_gettime(CLOCK_REALTIME, &res);
+    gettimeofday(&res, NULL);
+    return 1000.0*res.tv_sec + (double)res.tv_usec/1e3;
+}
+#endif
+
+
+static int frame_number_HR = 0;
+static int frame_number_LR = 0;
+
+int Init(int mID, int nmax)
+{
+        double  t0, t1, time_c;
+
+        if(mosaic[mID]!=NULL)
+        {
+                delete mosaic[mID];
+                mosaic[mID] = NULL;
+        }
+
+        mosaic[mID] = new Mosaic();
+
+        t0 = now_ms();
+
+        // When processing higher than 720x480 video, process low-res at
+        // quarter resolution
+        if(tWidth[LR]>180)
+            quarter_res[LR] = true;
+
+
+        // Check for initialization and if not, initialize
+        if (!mosaic[mID]->isInitialized())
+        {
+                mosaic[mID]->initialize(blendingType, stripType, tWidth[mID], tHeight[mID],
+                        nmax, quarter_res[mID], thresh_still[mID]);
+        }
+
+        t1 = now_ms();
+        time_c = t1 - t0;
+        LOGV("Init[%d]: %g ms [%d frames]",mID,time_c,nmax);
+        return 1;
+}
+
+void GenerateQuarterResImagePlanar(ImageType im, int input_w, int input_h,
+        ImageType &out)
+{
+    ImageType imp;
+    ImageType outp;
+
+    int count = 0;
+
+    for (int j = 0; j < input_h; j += H2L_FACTOR)
+    {
+        imp = im + j * input_w;
+        outp = out + (j / H2L_FACTOR) * (input_w / H2L_FACTOR);
+
+        for (int i = 0; i < input_w; i += H2L_FACTOR)
+        {
+            *outp++ = *(imp + i);
+            count++;
+        }
+    }
+
+    for (int j = input_h; j < 2 * input_h; j += H2L_FACTOR)
+    {
+        imp = im + j * input_w;
+        outp = out + (j / H2L_FACTOR) * (input_w / H2L_FACTOR);
+
+        for (int i = 0; i < input_w; i += H2L_FACTOR)
+        {
+            *outp++ = *(imp + i);
+            count++;
+        }
+    }
+
+    for (int j = 2 * input_h; j < 3 * input_h; j += H2L_FACTOR)
+    {
+        imp = im + j * input_w;
+        outp = out + (j / H2L_FACTOR) * (input_w / H2L_FACTOR);
+
+        for (int i = 0; i < input_w; i += H2L_FACTOR)
+        {
+            *outp++ = *(imp + i);
+            count++;
+        }
+    }
+}
+
+int AddFrame(int mID, int k, float* trs1d)
+{
+    double  t0, t1, time_c;
+    double trs[3][3];
+
+    int ret_code = mosaic[mID]->addFrame(tImage[mID][k]);
+
+    mosaic[mID]->getAligner()->getLastTRS(trs);
+
+    if(trs1d!=NULL)
+    {
+
+        trs1d[0] = trs[0][0];
+        trs1d[1] = trs[0][1];
+        trs1d[2] = trs[0][2];
+        trs1d[3] = trs[1][0];
+        trs1d[4] = trs[1][1];
+        trs1d[5] = trs[1][2];
+        trs1d[6] = trs[2][0];
+        trs1d[7] = trs[2][1];
+        trs1d[8] = trs[2][2];
+    }
+
+    return ret_code;
+}
+
+int Finalize(int mID)
+{
+    double  t0, t1, time_c;
+
+    t0 = now_ms();
+    // Create the mosaic
+    int ret = mosaic[mID]->createMosaic(gProgress[mID], gCancelComputation[mID]);
+    t1 = now_ms();
+    time_c = t1 - t0;
+    LOGV("CreateMosaic: %g ms",time_c);
+
+    // Get back the result
+    resultYVU = mosaic[mID]->getMosaic(mosaicWidth, mosaicHeight);
+
+    return ret;
+}
+
+void YUV420toYVU24(ImageType yvu24, ImageType yuv420sp, int width, int height)
+{
+    int frameSize = width * height;
+
+    ImageType oyp = yvu24;
+    ImageType ovp = yvu24+frameSize;
+    ImageType oup = yvu24+frameSize+frameSize;
+
+    for (int j = 0, yp = 0; j < height; j++)
+    {
+        unsigned char u = 0, v = 0;
+        int uvp = frameSize + (j >> 1) * width;
+        for (int i = 0; i < width; i++, yp++)
+        {
+            *oyp++ = yuv420sp[yp];
+            //int y = (0xff & (int)yuv420sp[yp]) -16;
+            //yvu24p[yp] = (y<0)?0:y;
+
+            if ((i & 1) == 0)
+            {
+                v = yuv420sp[uvp++];
+                u = yuv420sp[uvp++];
+            }
+
+            *ovp++ = v;
+            *oup++ = u;
+        }
+    }
+}
+
+void YUV420toYVU24_NEW(ImageType yvu24, ImageType yuv420sp, int width,
+        int height)
+{
+    int frameSize = width * height;
+
+    ImageType oyp = yvu24;
+    ImageType ovp = yvu24 + frameSize;
+    ImageType oup = yvu24 + frameSize + frameSize;
+
+    memcpy(yvu24, yuv420sp, frameSize * sizeof(unsigned char));
+
+    for (int j = 0; j < height; j += 2)
+    {
+        unsigned char u = 0, v = 0;
+        int uvp = frameSize + (j >> 1) * width;
+        ovp = yvu24 + frameSize + j * width;
+        oup = ovp + frameSize;
+
+        ImageType iuvp = yuv420sp + uvp;
+
+        for (int i = 0; i < width; i += 2)
+        {
+            v = *iuvp++;
+            u = *iuvp++;
+
+            *ovp++ = v;
+            *oup++ = u;
+
+            *ovp++ = v;
+            *oup++ = u;
+
+        }
+        memcpy(ovp, ovp - width, width * sizeof(unsigned char));
+        memcpy(oup, oup - width, width * sizeof(unsigned char));
+    }
+}
+
+
+JNIEXPORT void JNICALL Java_com_android_camera_Mosaic_allocateMosaicMemory(
+        JNIEnv* env, jobject thiz, jint width, jint height)
+{
+    tWidth[HR] = width;
+    tHeight[HR] = height;
+    tWidth[LR] = int(width / H2L_FACTOR);
+    tHeight[LR] = int(height / H2L_FACTOR);
+
+    for(int i=0; i<MAX_FRAMES; i++)
+    {
+            tImage[LR][i] = ImageUtils::allocateImage(tWidth[LR], tHeight[LR],
+                    ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+            tImage[HR][i] = ImageUtils::allocateImage(tWidth[HR], tHeight[HR],
+                    ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+    }
+
+    AllocateTextureMemory(tWidth[HR], tHeight[HR], tWidth[LR], tHeight[LR]);
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_Mosaic_freeMosaicMemory(
+        JNIEnv* env, jobject thiz)
+{
+    for(int i = 0; i < MAX_FRAMES; i++)
+    {
+        ImageUtils::freeImage(tImage[LR][i]);
+        ImageUtils::freeImage(tImage[HR][i]);
+    }
+
+    FreeTextureMemory();
+}
+
+
+void decodeYUV444SP(unsigned char* rgb, unsigned char* yuv420sp, int width,
+        int height)
+{
+    int frameSize = width * height;
+
+    for (int j = 0, yp = 0; j < height; j++)
+    {
+        int vp = frameSize + j * width, u = 0, v = 0;
+        int up = vp + frameSize;
+
+        for (int i = 0; i < width; i++, yp++, vp++, up++)
+        {
+            int y = (0xff & ((int) yuv420sp[yp])) - 16;
+            if (y < 0) y = 0;
+
+            v = (0xff & yuv420sp[vp]) - 128;
+            u = (0xff & yuv420sp[up]) - 128;
+
+            int y1192 = 1192 * y;
+            int r = (y1192 + 1634 * v);
+            int g = (y1192 - 833 * v - 400 * u);
+            int b = (y1192 + 2066 * u);
+
+            if (r < 0) r = 0; else if (r > 262143) r = 262143;
+            if (g < 0) g = 0; else if (g > 262143) g = 262143;
+            if (b < 0) b = 0; else if (b > 262143) b = 262143;
+
+            //rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
+            int p = j*width*3+i*3;
+            rgb[p+0] = (r<<6 & 0xFF0000)>>16;
+            rgb[p+1] = (g>>2 & 0xFF00)>>8;
+            rgb[p+2] =  b>>10 & 0xFF;
+        }
+    }
+}
+
+static int count = 0;
+
+void ConvertYVUAiToPlanarYVU(unsigned char *planar, unsigned char *in, int width,
+        int height)
+{
+    int planeSize = width * height;
+    unsigned char* Yptr = planar;
+    unsigned char* Vptr = planar + planeSize;
+    unsigned char* Uptr = Vptr + planeSize;
+
+    for (int i = 0; i < planeSize; i++)
+    {
+        *Yptr++ = *in++;
+        *Vptr++ = *in++;
+        *Uptr++ = *in++;
+        in++;   // Alpha
+    }
+}
+
+JNIEXPORT jfloatArray JNICALL Java_com_android_camera_Mosaic_setSourceImageFromGPU(
+        JNIEnv* env, jobject thiz)
+{
+    double  t0, t1, time_c;
+    t0 = now_ms();
+    int ret_code = Mosaic::MOSAIC_RET_OK;
+
+    if(frame_number_HR<MAX_FRAMES && frame_number_LR<MAX_FRAMES)
+    {
+        double last_tx = mTx;
+
+        sem_wait(&gPreviewImage_semaphore);
+        ConvertYVUAiToPlanarYVU(tImage[LR][frame_number_LR], gPreviewImage[LR],
+                tWidth[LR], tHeight[LR]);
+
+        sem_post(&gPreviewImage_semaphore);
+
+        ret_code = AddFrame(LR, frame_number_LR, gTRS);
+
+        if(ret_code == Mosaic::MOSAIC_RET_OK || ret_code == Mosaic::MOSAIC_RET_FEW_INLIERS)
+        {
+            // Copy into HR buffer only if this is a valid frame
+            sem_wait(&gPreviewImage_semaphore);
+            ConvertYVUAiToPlanarYVU(tImage[HR][frame_number_HR], gPreviewImage[HR],
+                    tWidth[HR], tHeight[HR]);
+            sem_post(&gPreviewImage_semaphore);
+
+            frame_number_LR++;
+            frame_number_HR++;
+        }
+    }
+    else
+    {
+        gTRS[1] = gTRS[2] = gTRS[3] = gTRS[5] = gTRS[6] = gTRS[7] = 0.0f;
+        gTRS[0] = gTRS[4] = gTRS[8] = 1.0f;
+    }
+
+    UpdateWarpTransformation(gTRS);
+
+    gTRS[9] = frame_number_HR;
+    gTRS[10] = ret_code;
+
+    jfloatArray bytes = env->NewFloatArray(11);
+    if(bytes != 0)
+    {
+        env->SetFloatArrayRegion(bytes, 0, 11, (jfloat*) gTRS);
+    }
+    return bytes;
+}
+
+
+
+JNIEXPORT jfloatArray JNICALL Java_com_android_camera_Mosaic_setSourceImage(
+        JNIEnv* env, jobject thiz, jbyteArray photo_data)
+{
+    double  t0, t1, time_c;
+    t0 = now_ms();
+
+    int ret_code = Mosaic::MOSAIC_RET_OK;
+
+    if(frame_number_HR<MAX_FRAMES && frame_number_LR<MAX_FRAMES)
+    {
+        jbyte *pixels = env->GetByteArrayElements(photo_data, 0);
+
+        YUV420toYVU24_NEW(tImage[HR][frame_number_HR], (ImageType)pixels,
+                tWidth[HR], tHeight[HR]);
+
+        env->ReleaseByteArrayElements(photo_data, pixels, 0);
+
+        double last_tx = mTx;
+
+        t0 = now_ms();
+        GenerateQuarterResImagePlanar(tImage[HR][frame_number_HR], tWidth[HR],
+                tHeight[HR], tImage[LR][frame_number_LR]);
+
+
+        sem_wait(&gPreviewImage_semaphore);
+        decodeYUV444SP(gPreviewImage[LR], tImage[LR][frame_number_LR],
+                gPreviewImageWidth[LR], gPreviewImageHeight[LR]);
+        sem_post(&gPreviewImage_semaphore);
+
+        ret_code = AddFrame(LR, frame_number_LR, gTRS);
+
+        if(ret_code == Mosaic::MOSAIC_RET_OK || ret_code == Mosaic::MOSAIC_RET_FEW_INLIERS)
+        {
+            frame_number_LR++;
+            frame_number_HR++;
+        }
+
+    }
+    else
+    {
+        gTRS[1] = gTRS[2] = gTRS[3] = gTRS[5] = gTRS[6] = gTRS[7] = 0.0f;
+        gTRS[0] = gTRS[4] = gTRS[8] = 1.0f;
+    }
+
+    UpdateWarpTransformation(gTRS);
+
+    gTRS[9] = frame_number_HR;
+    gTRS[10] = ret_code;
+
+    jfloatArray bytes = env->NewFloatArray(11);
+    if(bytes != 0)
+    {
+        env->SetFloatArrayRegion(bytes, 0, 11, (jfloat*) gTRS);
+    }
+    return bytes;
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_Mosaic_setBlendingType(
+        JNIEnv* env, jobject thiz, jint type)
+{
+    blendingType = int(type);
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_Mosaic_setStripType(
+        JNIEnv* env, jobject thiz, jint type)
+{
+    stripType = int(type);
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_Mosaic_reset(
+        JNIEnv* env, jobject thiz)
+{
+    frame_number_HR = 0;
+    frame_number_LR = 0;
+
+    gProgress[LR] = 0.0;
+    gProgress[HR] = 0.0;
+
+    gCancelComputation[LR] = false;
+    gCancelComputation[HR] = false;
+
+    Init(LR,MAX_FRAMES);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_camera_Mosaic_reportProgress(
+        JNIEnv* env, jobject thiz, jboolean hires, jboolean cancel_computation)
+{
+    if(bool(hires))
+        gCancelComputation[HR] = cancel_computation;
+    else
+        gCancelComputation[LR] = cancel_computation;
+
+    if(bool(hires))
+        return (jint) gProgress[HR];
+    else
+        return (jint) gProgress[LR];
+}
+
+JNIEXPORT jint JNICALL Java_com_android_camera_Mosaic_createMosaic(
+        JNIEnv* env, jobject thiz, jboolean value)
+{
+    high_res = bool(value);
+
+    int ret;
+
+    if(high_res)
+    {
+        LOGV("createMosaic() - High-Res Mode");
+        double  t0, t1, time_c;
+
+        gProgress[HR] = 0.0;
+        t0 = now_ms();
+
+        Init(HR, frame_number_HR);
+
+        for(int k = 0; k < frame_number_HR; k++)
+        {
+            if (gCancelComputation[HR])
+                break;
+            AddFrame(HR, k, NULL);
+            gProgress[HR] += TIME_PERCENT_ALIGN/frame_number_HR;
+        }
+
+        if (gCancelComputation[HR])
+        {
+            ret = Mosaic::MOSAIC_RET_CANCELLED;
+        }
+        else
+        {
+            gProgress[HR] = TIME_PERCENT_ALIGN;
+
+            t1 = now_ms();
+            time_c = t1 - t0;
+            LOGV("AlignAll - %d frames [HR]: %g ms", frame_number_HR, time_c);
+
+            ret = Finalize(HR);
+
+            gProgress[HR] = 100.0;
+        }
+
+        high_res = false;
+    }
+    else
+    {
+        LOGV("createMosaic() - Low-Res Mode");
+        gProgress[LR] = TIME_PERCENT_ALIGN;
+
+        ret = Finalize(LR);
+
+        gProgress[LR] = 100.0;
+    }
+
+    return (jint) ret;
+}
+
+JNIEXPORT jintArray JNICALL Java_com_android_camera_Mosaic_getFinalMosaic(
+        JNIEnv* env, jobject thiz)
+{
+    int y,x;
+    int width = mosaicWidth;
+    int height = mosaicHeight;
+    int imageSize = width * height;
+
+    // Convert back to RGB24
+    resultBGR = ImageUtils::allocateImage(mosaicWidth, mosaicHeight,
+            ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+    ImageUtils::yvu2bgr(resultBGR, resultYVU, mosaicWidth, mosaicHeight);
+
+    LOGV("MosBytes: %d, W = %d, H = %d", imageSize, width, height);
+
+    int* image = new int[imageSize];
+    int* dims = new int[2];
+
+    for(y=0; y<height; y++)
+    {
+        for(x=0; x<width; x++)
+        {
+            image[y*width+x] = (0xFF<<24) | (resultBGR[y*width*3+x*3+2]<<16)|
+                    (resultBGR[y*width*3+x*3+1]<<8)| (resultBGR[y*width*3+x*3]);
+        }
+    }
+
+    dims[0] = width;
+    dims[1] = height;
+
+    ImageUtils::freeImage(resultBGR);
+
+    jintArray bytes = env->NewIntArray(imageSize+2);
+    if (bytes == 0) {
+        LOGE("Error in creating the image.");
+        delete[] image;
+        return 0;
+    }
+    env->SetIntArrayRegion(bytes, 0, imageSize, (jint*) image);
+    env->SetIntArrayRegion(bytes, imageSize, 2, (jint*) dims);
+    delete[] image;
+    delete[] dims;
+    return bytes;
+}
+
+JNIEXPORT jbyteArray JNICALL Java_com_android_camera_Mosaic_getFinalMosaicNV21(
+        JNIEnv* env, jobject thiz)
+{
+    int y,x;
+    int width;
+    int height;
+
+    width = mosaicWidth;
+    height = mosaicHeight;
+
+    int imageSize = 1.5*width * height;
+
+    // Convert YVU to NV21 format in-place
+    ImageType V = resultYVU+mosaicWidth*mosaicHeight;
+    ImageType U = V+mosaicWidth*mosaicHeight;
+    for(int j=0; j<mosaicHeight/2; j++)
+    {
+        for(int i=0; i<mosaicWidth; i+=2)
+        {
+            V[j*mosaicWidth+i] = V[(2*j)*mosaicWidth+i];        // V
+            V[j*mosaicWidth+i+1] = U[(2*j)*mosaicWidth+i];        // U
+        }
+    }
+
+    LOGV("MosBytes: %d, W = %d, H = %d", imageSize, width, height);
+
+    unsigned char* dims = new unsigned char[8];
+
+    dims[0] = (unsigned char)(width >> 24);
+    dims[1] = (unsigned char)(width >> 16);
+    dims[2] = (unsigned char)(width >> 8);
+    dims[3] = (unsigned char)width;
+
+    dims[4] = (unsigned char)(height >> 24);
+    dims[5] = (unsigned char)(height >> 16);
+    dims[6] = (unsigned char)(height >> 8);
+    dims[7] = (unsigned char)height;
+
+    jbyteArray bytes = env->NewByteArray(imageSize+8);
+    if (bytes == 0) {
+        LOGE("Error in creating the image.");
+        ImageUtils::freeImage(resultYVU);
+        return 0;
+    }
+    env->SetByteArrayRegion(bytes, 0, imageSize, (jbyte*) resultYVU);
+    env->SetByteArrayRegion(bytes, imageSize, 8, (jbyte*) dims);
+    delete[] dims;
+    ImageUtils::freeImage(resultYVU);
+    return bytes;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_bundle.h b/jni_mosaic/feature_stab/db_vlvm/db_bundle.h
new file mode 100644
index 0000000..e4fb8db
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_bundle.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_bundle.h,v 1.2 2011/06/17 14:03:30 mbansal Exp $ */
+
+#ifndef DB_BUNDLE_H
+#define DB_BUNDLE_H
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMBundle (LM) Bundle adjustment utilities (a.k.a. Levenberg-Marquardt algorithm)
+ */
+/*\{*/
+
+#include "db_utilities.h"
+
+/*!
+Solve for update dx such that diagmult(1+lambda,transpose(J)%J)%dx= -Jtf
+using only upper half of JtJ, destroying lower half below diagonal in the process
+dimension is n and d should point to n allocated doubles of scratch memory
+*/
+inline void db_Compute_dx(double *dx,double **JtJ,double *min_Jtf,double lambda,double *d,int n)
+{
+    int i;
+    double opl;
+
+    opl=1.0+lambda;
+    for(i=0;i<n;i++) d[i]=JtJ[i][i]*opl;
+
+    db_CholeskyDecompSeparateDiagonal(JtJ,d,n);
+    db_CholeskyBacksub(dx,JtJ,d,n,min_Jtf);
+}
+
+/*!
+Solve for update dx such that diagmult(1+lambda,transpose(J)%J)%dx= -Jtf
+using only upper half of JtJ, destroying lower half below diagonal in the process
+*/
+inline void db_Compute_dx_3x3(double dx[3],double JtJ[9],const double min_Jtf[3],double lambda)
+{
+    double d[3],opl;
+
+    opl=1.0+lambda;
+    d[0]=JtJ[0]*opl;
+    d[1]=JtJ[4]*opl;
+    d[2]=JtJ[8]*opl;
+    db_CholeskyDecomp3x3SeparateDiagonal(JtJ,d);
+    db_CholeskyBacksub3x3(dx,JtJ,d,min_Jtf);
+}
+
+/*\}*/
+
+#endif /* DB_BUNDLE_H */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.cpp b/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.cpp
new file mode 100644
index 0000000..28cb4a7
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.cpp
@@ -0,0 +1,1770 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*$Id: db_feature_detection.cpp,v 1.4 2011/06/17 14:03:30 mbansal Exp $*/
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_utilities.h"
+#include "db_feature_detection.h"
+#ifdef _VERBOSE_
+#include <iostream>
+#endif
+#include <float.h>
+
+#define DB_SUB_PIXEL
+
+#define BORDER 10 // 5
+
+float** db_AllocStrengthImage_f(float **im,int w,int h)
+{
+    int i,n,aw;
+    long c,size;
+    float **img,*aim,*p;
+
+    /*Determine number of 124 element chunks needed*/
+    n=(db_maxi(1,w-6)+123)/124;
+    /*Determine the total allocation width aw*/
+    aw=n*124+8;
+    /*Allocate*/
+    size=aw*h+16;
+    *im=new float [size];
+    /*Clean up*/
+    p=(*im);
+    for(c=0;c<size;c++) p[c]=0.0;
+    /*Get a 16 byte aligned pointer*/
+    aim=db_AlignPointer_f(*im,16);
+    /*Allocate pointer table*/
+    img=new float* [h];
+    /*Initialize the pointer table*/
+    for(i=0;i<h;i++)
+    {
+        img[i]=aim+aw*i+1;
+    }
+
+    return(img);
+}
+
+void db_FreeStrengthImage_f(float *im,float **img,int h)
+{
+    delete [] im;
+    delete [] img;
+}
+
+/*Compute derivatives Ix,Iy for a subrow of img with upper left (i,j) and width chunk_width
+Memory references occur one pixel outside the subrow*/
+inline void db_IxIyRow_f(float *Ix,float *Iy,const float * const *img,int i,int j,int chunk_width)
+{
+    int c;
+
+    for(c=0;c<chunk_width;c++)
+    {
+        Ix[c]=img[i][j+c-1]-img[i][j+c+1];
+        Iy[c]=img[i-1][j+c]-img[i+1][j+c];
+    }
+}
+
+/*Compute derivatives Ix,Iy for a subrow of img with upper left (i,j) and width 128
+Memory references occur one pixel outside the subrow*/
+inline void db_IxIyRow_u(int *dxx,const unsigned char * const *img,int i,int j,int nc)
+{
+#ifdef DB_USE_MMX
+    const unsigned char *r1,*r2,*r3;
+
+    r1=img[i-1]+j; r2=img[i]+j; r3=img[i+1]+j;
+
+    _asm
+    {
+        mov esi,16
+        mov eax,r1
+        mov ebx,r2
+        mov ecx,r3
+        mov edx,dxx
+
+        /*Get bitmask into mm7*/
+        mov       edi,7F7F7F7Fh
+        movd      mm7,edi
+        punpckldq mm7,mm7
+
+loopstart:
+        /***************dx part 1-12*********************************/
+        movq       mm0,[eax]       /*1 Get upper*/
+         pxor      mm6,mm6         /*2 Set to zero*/
+        movq       mm1,[ecx]       /*3 Get lower*/
+         psrlq     mm0,1           /*4 Shift*/
+        psrlq      mm1,1           /*5 Shift*/
+         pand      mm0,mm7         /*6 And*/
+        movq       mm2,[ebx-1]     /*13 Get left*/
+         pand      mm1,mm7         /*7 And*/
+        psubb      mm0,mm1         /*8 Subtract*/
+         pxor      mm5,mm5         /*14 Set to zero*/
+        movq       mm1,mm0         /*9 Copy*/
+         pcmpgtb   mm6,mm0         /*10 Create unpack mask*/
+        movq       mm3,[ebx+1]     /*15 Get right*/
+         punpcklbw mm0,mm6         /*11 Unpack low*/
+        punpckhbw  mm1,mm6         /*12 Unpack high*/
+        /***************dy part 13-24*********************************/
+         movq      mm4,mm0         /*25 Copy dx*/
+        psrlq      mm2,1           /*16 Shift*/
+         pmullw    mm0,mm0         /*26 Multiply dx*dx*/
+        psrlq      mm3,1           /*17 Shift*/
+         pand      mm2,mm7         /*18 And*/
+        pand       mm3,mm7         /*19 And*/
+         /*Stall*/
+        psubb      mm2,mm3         /*20 Subtract*/
+         /*Stall*/
+        movq       mm3,mm2         /*21 Copy*/
+         pcmpgtb   mm5,mm2         /*22 Create unpack mask*/
+        punpcklbw  mm2,mm5         /*23 Unpack low*/
+         /*Stall*/
+        punpckhbw  mm3,mm5         /*24 Unpack high*/
+        /***************dxx dxy dyy low part 25-49*********************************/
+         pmullw    mm4,mm2         /*27 Multiply dx*dy*/
+        pmullw     mm2,mm2         /*28 Multiply dy*dy*/
+         pxor      mm6,mm6         /*29 Set to zero*/
+        movq       mm5,mm0         /*30 Copy dx*dx*/
+         pcmpgtw   mm6,mm0         /*31 Create unpack mask for dx*dx*/
+        punpcklwd  mm0,mm6         /*32 Unpack dx*dx lows*/
+         /*Stall*/
+        punpckhwd  mm5,mm6         /*33 Unpack dx*dx highs*/
+         pxor      mm6,mm6         /*36 Set to zero*/
+        movq       [edx],mm0       /*34 Store dx*dx lows*/
+         movq      mm0,mm4         /*37 Copy dx*dy*/
+        movq       [edx+8],mm5     /*35 Store dx*dx highs*/
+         pcmpgtw   mm6,mm4         /*38 Create unpack mask for dx*dy*/
+        punpcklwd  mm4,mm6         /*39 Unpack dx*dy lows*/
+         /*Stall*/
+        punpckhwd  mm0,mm6         /*40 Unpack dx*dy highs*/
+         pxor      mm6,mm6         /*43 Set to zero*/
+        movq       [edx+512],mm4   /*41 Store dx*dy lows*/
+         movq      mm5,mm2         /*44 Copy dy*dy*/
+        movq       [edx+520],mm0   /*42 Store dx*dy highs*/
+         pcmpgtw   mm6,mm2         /*45 Create unpack mask for dy*dy*/
+        punpcklwd  mm2,mm6         /*46 Unpack dy*dy lows*/
+         movq      mm4,mm1         /*50 Copy dx*/
+        punpckhwd  mm5,mm6         /*47 Unpack dy*dy highs*/
+         pmullw    mm1,mm1         /*51 Multiply dx*dx*/
+        movq       [edx+1024],mm2  /*48 Store dy*dy lows*/
+         pmullw    mm4,mm3         /*52 Multiply dx*dy*/
+        movq       [edx+1032],mm5  /*49 Store dy*dy highs*/
+        /***************dxx dxy dyy high part 50-79*********************************/
+         pmullw    mm3,mm3         /*53 Multiply dy*dy*/
+        pxor       mm6,mm6         /*54 Set to zero*/
+         movq      mm5,mm1         /*55 Copy dx*dx*/
+        pcmpgtw    mm6,mm1         /*56 Create unpack mask for dx*dx*/
+         pxor      mm2,mm2         /*61 Set to zero*/
+        punpcklwd  mm1,mm6         /*57 Unpack dx*dx lows*/
+         movq      mm0,mm4         /*62 Copy dx*dy*/
+        punpckhwd  mm5,mm6         /*58 Unpack dx*dx highs*/
+         pcmpgtw   mm2,mm4         /*63 Create unpack mask for dx*dy*/
+        movq       [edx+16],mm1    /*59 Store dx*dx lows*/
+         punpcklwd mm4,mm2         /*64 Unpack dx*dy lows*/
+        movq       [edx+24],mm5    /*60 Store dx*dx highs*/
+         punpckhwd mm0,mm2         /*65 Unpack dx*dy highs*/
+        movq       [edx+528],mm4   /*66 Store dx*dy lows*/
+         pxor      mm6,mm6         /*68 Set to zero*/
+        movq       [edx+536],mm0   /*67 Store dx*dy highs*/
+         movq      mm5,mm3         /*69 Copy dy*dy*/
+        pcmpgtw    mm6,mm3         /*70 Create unpack mask for dy*dy*/
+         add       eax,8           /*75*/
+        punpcklwd  mm3,mm6         /*71 Unpack dy*dy lows*/
+         add       ebx,8           /*76*/
+        punpckhwd  mm5,mm6         /*72 Unpack dy*dy highs*/
+         add       ecx,8           /*77*/
+        movq       [edx+1040],mm3  /*73 Store dy*dy lows*/
+         /*Stall*/
+        movq       [edx+1048],mm5  /*74 Store dy*dy highs*/
+         /*Stall*/
+        add        edx,32          /*78*/
+         dec esi                   /*79*/
+        jnz loopstart
+
+        emms
+    }
+
+#else
+    int c;
+    int Ix,Iy;
+
+    for(c=0;c<nc;c++)
+    {
+        Ix=(img[i][j+c-1]-img[i][j+c+1])>>1;
+        Iy=(img[i-1][j+c]-img[i+1][j+c])>>1;
+        dxx[c]=Ix*Ix;
+        dxx[c+128]=Ix*Iy;
+        dxx[c+256]=Iy*Iy;
+    }
+#endif /*DB_USE_MMX*/
+}
+
+/*Filter vertically five rows of derivatives of length chunk_width into gxx,gxy,gyy*/
+inline void db_gxx_gxy_gyy_row_f(float *gxx,float *gxy,float *gyy,int chunk_width,
+                                 float *Ix0,float *Ix1,float *Ix2,float *Ix3,float *Ix4,
+                                 float *Iy0,float *Iy1,float *Iy2,float *Iy3,float *Iy4)
+{
+    int c;
+    float dx,dy;
+    float Ixx0,Ixy0,Iyy0,Ixx1,Ixy1,Iyy1,Ixx2,Ixy2,Iyy2,Ixx3,Ixy3,Iyy3,Ixx4,Ixy4,Iyy4;
+
+    for(c=0;c<chunk_width;c++)
+    {
+        dx=Ix0[c];
+        dy=Iy0[c];
+        Ixx0=dx*dx;
+        Ixy0=dx*dy;
+        Iyy0=dy*dy;
+
+        dx=Ix1[c];
+        dy=Iy1[c];
+        Ixx1=dx*dx;
+        Ixy1=dx*dy;
+        Iyy1=dy*dy;
+
+        dx=Ix2[c];
+        dy=Iy2[c];
+        Ixx2=dx*dx;
+        Ixy2=dx*dy;
+        Iyy2=dy*dy;
+
+        dx=Ix3[c];
+        dy=Iy3[c];
+        Ixx3=dx*dx;
+        Ixy3=dx*dy;
+        Iyy3=dy*dy;
+
+        dx=Ix4[c];
+        dy=Iy4[c];
+        Ixx4=dx*dx;
+        Ixy4=dx*dy;
+        Iyy4=dy*dy;
+
+        /*Filter vertically*/
+        gxx[c]=Ixx0+Ixx1*4.0f+Ixx2*6.0f+Ixx3*4.0f+Ixx4;
+        gxy[c]=Ixy0+Ixy1*4.0f+Ixy2*6.0f+Ixy3*4.0f+Ixy4;
+        gyy[c]=Iyy0+Iyy1*4.0f+Iyy2*6.0f+Iyy3*4.0f+Iyy4;
+    }
+}
+
+/*Filter vertically five rows of derivatives of length 128 into gxx,gxy,gyy*/
+inline void db_gxx_gxy_gyy_row_s(int *g,int *d0,int *d1,int *d2,int *d3,int *d4,int nc)
+{
+#ifdef DB_USE_MMX
+    int c;
+
+    _asm
+    {
+        mov c,64
+        mov eax,d0
+        mov ebx,d1
+        mov ecx,d2
+        mov edx,d3
+        mov edi,d4
+        mov esi,g
+
+loopstart:
+        /***************dxx part 1-14*********************************/
+        movq        mm0,[eax]      /*1 Get dxx0*/
+         /*Stall*/
+        movq        mm1,[ebx]      /*2 Get dxx1*/
+         /*Stall*/
+        movq        mm2,[ecx]      /*5 Get dxx2*/
+         pslld      mm1,2          /*3 Shift dxx1*/
+        movq        mm3,[edx]      /*10 Get dxx3*/
+         paddd      mm0,mm1        /*4 Accumulate dxx1*/
+        movq        mm4,[eax+512]  /*15 Get dxy0*/
+         pslld      mm2,1          /*6 Shift dxx2 1*/
+        paddd       mm0,mm2        /*7 Accumulate dxx2 1*/
+         pslld      mm2,1          /*8 Shift dxx2 2*/
+        movq        mm5,[ebx+512]  /*16 Get dxy1*/
+         paddd      mm0,mm2        /*9 Accumulate dxx2 2*/
+        pslld       mm3,2          /*11 Shift dxx3*/
+         /*Stall*/
+        paddd       mm0,mm3        /*12 Accumulate dxx3*/
+         pslld      mm5,2          /*17 Shift dxy1*/
+        paddd       mm0,[edi]      /*13 Accumulate dxx4*/
+         paddd      mm4,mm5        /*18 Accumulate dxy1*/
+        movq        mm6,[ecx+512]  /*19 Get dxy2*/
+         /*Stall*/
+        movq        [esi],mm0      /*14 Store dxx sums*/
+        /***************dxy part 15-28*********************************/
+         pslld      mm6,1          /*20 Shift dxy2 1*/
+        paddd       mm4,mm6        /*21 Accumulate dxy2 1*/
+         pslld      mm6,1          /*22 Shift dxy2 2*/
+        movq        mm0,[eax+1024] /*29 Get dyy0*/
+         paddd      mm4,mm6        /*23 Accumulate dxy2 2*/
+        movq        mm7,[edx+512]  /*24 Get dxy3*/
+         pslld      mm7,2          /*25 Shift dxy3*/
+        movq        mm1,[ebx+1024] /*30 Get dyy1*/
+         paddd      mm4,mm7        /*26 Accumulate dxy3*/
+        paddd       mm4,[edi+512]  /*27 Accumulate dxy4*/
+         pslld      mm1,2          /*31 Shift dyy1*/
+        movq        mm2,[ecx+1024] /*33 Get dyy2*/
+         paddd      mm0,mm1        /*32 Accumulate dyy1*/
+        movq        [esi+512],mm4  /*28 Store dxy sums*/
+         pslld      mm2,1          /*34 Shift dyy2 1*/
+        /***************dyy part 29-49*********************************/
+
+
+        movq        mm3,[edx+1024] /*38 Get dyy3*/
+         paddd      mm0,mm2        /*35 Accumulate dyy2 1*/
+        paddd       mm0,[edi+1024] /*41 Accumulate dyy4*/
+         pslld      mm2,1          /*36 Shift dyy2 2*/
+        paddd       mm0,mm2        /*37 Accumulate dyy2 2*/
+         pslld      mm3,2          /*39 Shift dyy3*/
+        paddd       mm0,mm3        /*40 Accumulate dyy3*/
+         add        eax,8           /*43*/
+        add         ebx,8           /*44*/
+         add        ecx,8           /*45*/
+        movq        [esi+1024],mm0 /*42 Store dyy sums*/
+         /*Stall*/
+        add         edx,8           /*46*/
+         add        edi,8           /*47*/
+        add         esi,8           /*48*/
+         dec        c               /*49*/
+        jnz         loopstart
+
+        emms
+    }
+
+#else
+    int c,dd;
+
+    for(c=0;c<nc;c++)
+    {
+        /*Filter vertically*/
+        dd=d2[c];
+        g[c]=d0[c]+(d1[c]<<2)+(dd<<2)+(dd<<1)+(d3[c]<<2)+d4[c];
+
+        dd=d2[c+128];
+        g[c+128]=d0[c+128]+(d1[c+128]<<2)+(dd<<2)+(dd<<1)+(d3[c+128]<<2)+d4[c+128];
+
+        dd=d2[c+256];
+        g[c+256]=d0[c+256]+(d1[c+256]<<2)+(dd<<2)+(dd<<1)+(d3[c+256]<<2)+d4[c+256];
+    }
+#endif /*DB_USE_MMX*/
+}
+
+/*Filter horizontally the three rows gxx,gxy,gyy into the strength subrow starting at i,j
+and with width chunk_width. gxx,gxy and gyy are assumed to be four pixels wider than chunk_width
+and starting at (i,j-2)*/
+inline void db_HarrisStrength_row_f(float **s,float *gxx,float *gxy,float *gyy,int i,int j,int chunk_width)
+{
+    float Gxx,Gxy,Gyy,det,trc;
+    int c;
+
+    for(c=0;c<chunk_width;c++)
+    {
+        Gxx=gxx[c]+gxx[c+1]*4.0f+gxx[c+2]*6.0f+gxx[c+3]*4.0f+gxx[c+4];
+        Gxy=gxy[c]+gxy[c+1]*4.0f+gxy[c+2]*6.0f+gxy[c+3]*4.0f+gxy[c+4];
+        Gyy=gyy[c]+gyy[c+1]*4.0f+gyy[c+2]*6.0f+gyy[c+3]*4.0f+gyy[c+4];
+
+        det=Gxx*Gyy-Gxy*Gxy;
+        trc=Gxx+Gyy;
+        s[i][j+c]=det-0.06f*trc*trc;
+    }
+}
+
+/*Filter g of length 128 in place with 14641. Output is shifted two steps
+and of length 124*/
+inline void db_Filter14641_128_i(int *g,int nc)
+{
+#ifdef DB_USE_MMX
+    int mask;
+
+    mask=0xFFFFFFFF;
+    _asm
+    {
+        mov esi,31
+        mov eax,g
+
+        /*Get bitmask 00000000FFFFFFFF into mm7*/
+        movd mm7,mask
+
+        /*Warming iteration one 1-16********************/
+        movq       mm6,[eax]      /*1 Load new data*/
+        paddd      mm0,mm6        /*2 Add 1* behind two steps*/
+        movq       mm2,mm6        /*3 Start with 1* in front two steps*/
+        pslld      mm6,1          /*4*/
+        paddd      mm1,mm6        /*5 Add 2* same place*/
+        pslld      mm6,1          /*6*/
+        paddd      mm1,mm6        /*7 Add 4* same place*/
+        pshufw     mm6,mm6,4Eh    /*8 Swap the two double-words using bitmask 01001110=4Eh*/
+        paddd      mm1,mm6        /*9 Add 4* swapped*/
+        movq       mm5,mm6        /*10 Copy*/
+        pand       mm6,mm7        /*11 Get low double-word only*/
+        paddd      mm2,mm6        /*12 Add 4* in front one step*/
+        pxor       mm6,mm5        /*13 Get high double-word only*/
+        paddd      mm0,mm6        /*14 Add 4* behind one step*/
+        movq       mm0,mm1        /*15 Shift along*/
+        movq       mm1,mm2        /*16 Shift along*/
+        /*Warming iteration two 17-32********************/
+        movq       mm4,[eax+8]    /*17 Load new data*/
+        paddd      mm0,mm4        /*18 Add 1* behind two steps*/
+        movq       mm2,mm4        /*19 Start with 1* in front two steps*/
+        pslld      mm4,1          /*20*/
+        paddd      mm1,mm4        /*21 Add 2* same place*/
+        pslld      mm4,1          /*22*/
+        paddd      mm1,mm4        /*23 Add 4* same place*/
+        pshufw     mm4,mm4,4Eh    /*24 Swap the two double-words using bitmask 01001110=4Eh*/
+        paddd      mm1,mm4        /*25 Add 4* swapped*/
+        movq       mm3,mm4        /*26 Copy*/
+        pand       mm4,mm7        /*27 Get low double-word only*/
+        paddd      mm2,mm4        /*28 Add 4* in front one step*/
+        pxor       mm4,mm3        /*29 Get high double-word only*/
+        paddd      mm0,mm4        /*30 Add 4* behind one step*/
+        movq       mm0,mm1        /*31 Shift along*/
+        movq       mm1,mm2        /*32 Shift along*/
+
+        /*Loop********************/
+loopstart:
+        /*First part of loop 33-47********/
+        movq        mm6,[eax+16]   /*33 Load new data*/
+         /*Stall*/
+        paddd       mm0,mm6        /*34 Add 1* behind two steps*/
+         movq       mm2,mm6        /*35 Start with 1* in front two steps*/
+        movq        mm4,[eax+24]   /*48 Load new data*/
+         pslld      mm6,1          /*36*/
+        paddd       mm1,mm6        /*37 Add 2* same place*/
+         pslld      mm6,1          /*38*/
+        paddd       mm1,mm6        /*39 Add 4* same place*/
+         pshufw     mm6,mm6,4Eh    /*40 Swap the two double-words using bitmask 01001110=4Eh*/
+        paddd       mm1,mm4        /*49 Add 1* behind two steps*/
+         movq       mm5,mm6        /*41 Copy*/
+        paddd       mm1,mm6        /*42 Add 4* swapped*/
+         pand       mm6,mm7        /*43 Get low double-word only*/
+        paddd       mm2,mm6        /*44 Add 4* in front one step*/
+         pxor       mm6,mm5        /*45 Get high double-word only*/
+        paddd       mm0,mm6        /*46 Add 4* behind one step*/
+         movq       mm6,mm4        /*50a Copy*/
+        pslld       mm4,1          /*51*/
+         /*Stall*/
+        movq        [eax],mm0      /*47 Store result two steps behind*/
+        /*Second part of loop 48-66********/
+         movq       mm0,mm6        /*50b Start with 1* in front two steps*/
+        paddd       mm2,mm4        /*52 Add 2* same place*/
+         pslld      mm4,1          /*53*/
+        paddd       mm2,mm4        /*54 Add 4* same place*/
+         pshufw     mm4,mm4,4Eh    /*55 Swap the two double-words using bitmask 01001110=4Eh*/
+        paddd       mm2,mm4        /*56 Add 4* swapped*/
+         movq       mm3,mm4        /*57 Copy*/
+        pand        mm4,mm7        /*58 Get low double-word only*/
+         /*Stall*/
+        paddd       mm0,mm4        /*59 Add 4* in front one step*/
+         pxor       mm4,mm3        /*60 Get high double-word only*/
+        paddd       mm1,mm4        /*61 Add 4* behind one step*/
+         add        eax,16         /*65*/
+        dec         esi            /*66*/
+         /*Stall*/
+        movq        [eax-8],mm1    /*62 Store result two steps behind*/
+         movq       mm1,mm0        /*63 Shift along*/
+        movq        mm0,mm2        /*64 Shift along*/
+        jnz loopstart
+
+        emms
+    }
+
+#else
+    int c;
+
+    for(c=0;c<nc-4;c++)
+    {
+        g[c]=g[c]+(g[c+1]<<2)+(g[c+2]<<2)+(g[c+2]<<1)+(g[c+3]<<2)+g[c+4];
+    }
+#endif /*DB_USE_MMX*/
+}
+
+/*Filter horizontally the three rows gxx,gxy,gyy of length 128 into the strength subrow s
+of length 124. gxx,gxy and gyy are assumed to be starting at (i,j-2) if s[i][j] is sought.
+s should be 16 byte aligned*/
+inline void db_HarrisStrength_row_s(float *s,int *gxx,int *gxy,int *gyy,int nc)
+{
+    float k;
+
+    k=0.06f;
+
+    db_Filter14641_128_i(gxx,nc);
+    db_Filter14641_128_i(gxy,nc);
+    db_Filter14641_128_i(gyy,nc);
+
+#ifdef DB_USE_SIMD
+
+
+    _asm
+    {
+        mov esi,15
+        mov eax,gxx
+        mov ebx,gxy
+        mov ecx,gyy
+        mov edx,s
+
+        /*broadcast k to all positions of xmm7*/
+        movss   xmm7,k
+        shufps  xmm7,xmm7,0
+
+        /*****Warm up 1-10**************************************/
+        cvtpi2ps  xmm0,[eax+8] /*1 Convert two integers into floating point of low double-word*/
+         /*Stall*/
+        cvtpi2ps  xmm1,[ebx+8] /*4 Convert two integers into floating point of low double-word*/
+         movlhps  xmm0,xmm0    /*2 Move them to the high double-word*/
+        cvtpi2ps  xmm2,[ecx+8] /*7 Convert two integers into floating point of low double-word*/
+         movlhps  xmm1,xmm1    /*5 Move them to the high double-word*/
+        cvtpi2ps  xmm0,[eax]   /*3 Convert two integers into floating point of low double-word*/
+         movlhps  xmm2,xmm2    /*8 Move them to the high double-word*/
+        cvtpi2ps  xmm1,[ebx]   /*6 Convert two integers into floating point of low double-word*/
+         movaps   xmm3,xmm0    /*10 Copy Cxx*/
+        cvtpi2ps  xmm2,[ecx]   /*9 Convert two integers into floating point of low double-word*/
+         /*Stall*/
+loopstart:
+        /*****First part of loop 11-18***********************/
+        mulps     xmm0,xmm2     /*11 Multiply to get Gxx*Gyy*/
+         addps    xmm2,xmm3     /*12 Add to get Gxx+Gyy*/
+        cvtpi2ps  xmm4,[eax+24] /*19 Convert two integers into floating point of low double-word*/
+         mulps    xmm1,xmm1     /*13 Multiply to get Gxy*Gxy*/
+        mulps     xmm2,xmm2     /*14 Multiply to get (Gxx+Gyy)*(Gxx+Gyy)*/
+         movlhps  xmm4,xmm4     /*20 Move them to the high double-word*/
+        cvtpi2ps  xmm4,[eax+16] /*21 Convert two integers into floating point of low double-word*/
+         /*Stall*/
+        subps     xmm0,xmm1     /*15 Subtract to get Gxx*Gyy-Gxy*Gxy*/
+         mulps    xmm2,xmm7     /*16 Multiply to get k*(Gxx+Gyy)*(Gxx+Gyy)*/
+        cvtpi2ps  xmm5,[ebx+24] /*22 Convert two integers into floating point of low double-word*/
+         /*Stall*/
+        movlhps   xmm5,xmm5     /*23 Move them to the high double-word*/
+         /*Stall*/
+        cvtpi2ps  xmm5,[ebx+16] /*24 Convert two integers into floating point of low double-word*/
+         subps    xmm0,xmm2     /*17 Subtract to get Gxx*Gyy-Gxy*Gxy-k*(Gxx+Gyy)*(Gxx+Gyy)*/
+        cvtpi2ps  xmm6,[ecx+24] /*25 Convert two integers into floating point of low double-word*/
+         /*Stall*/
+        movaps    [edx],xmm0    /*18 Store*/
+        /*****Second part of loop 26-40***********************/
+         movlhps  xmm6,xmm6     /*26 Move them to the high double-word*/
+        cvtpi2ps  xmm6,[ecx+16] /*27 Convert two integers into floating point of low double-word*/
+         movaps   xmm3,xmm4     /*28 Copy Cxx*/
+        mulps     xmm4,xmm6     /*29 Multiply to get Gxx*Gyy*/
+         addps    xmm6,xmm3     /*30 Add to get Gxx+Gyy*/
+        cvtpi2ps  xmm0,[eax+40] /*(1 Next) Convert two integers into floating point of low double-word*/
+         mulps    xmm5,xmm5     /*31 Multiply to get Gxy*Gxy*/
+        cvtpi2ps  xmm1,[ebx+40] /*(4 Next) Convert two integers into floating point of low double-word*/
+         mulps    xmm6,xmm6     /*32 Multiply to get (Gxx+Gyy)*(Gxx+Gyy)*/
+        cvtpi2ps  xmm2,[ecx+40] /*(7 Next) Convert two integers into floating point of low double-word*/
+         movlhps  xmm0,xmm0     /*(2 Next) Move them to the high double-word*/
+        subps     xmm4,xmm5     /*33 Subtract to get Gxx*Gyy-Gxy*Gxy*/
+         movlhps  xmm1,xmm1     /*(5 Next) Move them to the high double-word*/
+        cvtpi2ps  xmm0,[eax+32] /*(3 Next)Convert two integers into floating point of low double-word*/
+         mulps    xmm6,xmm7     /*34 Multiply to get k*(Gxx+Gyy)*(Gxx+Gyy)*/
+        cvtpi2ps  xmm1,[ebx+32] /*(6 Next) Convert two integers into floating point of low double-word*/
+         movlhps  xmm2,xmm2     /*(8 Next) Move them to the high double-word*/
+        movaps    xmm3,xmm0     /*(10 Next) Copy Cxx*/
+         add      eax,32        /*37*/
+        subps     xmm4,xmm6     /*35 Subtract to get Gxx*Gyy-Gxy*Gxy-k*(Gxx+Gyy)*(Gxx+Gyy)*/
+         add      ebx,32        /*38*/
+        cvtpi2ps  xmm2,[ecx+32] /*(9 Next) Convert two integers into floating point of low double-word*/
+         /*Stall*/
+        movaps    [edx+16],xmm4 /*36 Store*/
+         /*Stall*/
+        add       ecx,32        /*39*/
+         add      edx,32        /*40*/
+        dec       esi           /*41*/
+        jnz loopstart
+
+        /****Cool down***************/
+        mulps    xmm0,xmm2    /*Multiply to get Gxx*Gyy*/
+        addps    xmm2,xmm3    /*Add to get Gxx+Gyy*/
+        mulps    xmm1,xmm1    /*Multiply to get Gxy*Gxy*/
+        mulps    xmm2,xmm2    /*Multiply to get (Gxx+Gyy)*(Gxx+Gyy)*/
+        subps    xmm0,xmm1    /*Subtract to get Gxx*Gyy-Gxy*Gxy*/
+        mulps    xmm2,xmm7    /*Multiply to get k*(Gxx+Gyy)*(Gxx+Gyy)*/
+        subps    xmm0,xmm2    /*Subtract to get Gxx*Gyy-Gxy*Gxy-k*(Gxx+Gyy)*(Gxx+Gyy)*/
+        movaps   [edx],xmm0   /*Store*/
+    }
+
+#else
+    float Gxx,Gxy,Gyy,det,trc;
+    int c;
+
+    //for(c=0;c<124;c++)
+    for(c=0;c<nc-4;c++)
+    {
+        Gxx=(float)gxx[c];
+        Gxy=(float)gxy[c];
+        Gyy=(float)gyy[c];
+
+        det=Gxx*Gyy-Gxy*Gxy;
+        trc=Gxx+Gyy;
+        s[c]=det-k*trc*trc;
+    }
+#endif /*DB_USE_SIMD*/
+}
+
+/*Compute the Harris corner strength of the chunk [left,top,right,bottom] of img and
+store it into the corresponding region of s. left and top have to be at least 3 and
+right and bottom have to be at most width-4,height-4*/
+inline void db_HarrisStrengthChunk_f(float **s,const float * const *img,int left,int top,int right,int bottom,
+                                      /*temp should point to at least
+                                      13*(right-left+5) of allocated memory*/
+                                      float *temp)
+{
+    float *Ix[5],*Iy[5];
+    float *gxx,*gxy,*gyy;
+    int i,chunk_width,chunk_width_p4;
+
+    chunk_width=right-left+1;
+    chunk_width_p4=chunk_width+4;
+    gxx=temp;
+    gxy=gxx+chunk_width_p4;
+    gyy=gxy+chunk_width_p4;
+    for(i=0;i<5;i++)
+    {
+        Ix[i]=gyy+chunk_width_p4+(2*i*chunk_width_p4);
+        Iy[i]=Ix[i]+chunk_width_p4;
+    }
+
+    /*Fill four rows of the wrap-around derivative buffers*/
+    for(i=top-2;i<top+2;i++) db_IxIyRow_f(Ix[i%5],Iy[i%5],img,i,left-2,chunk_width_p4);
+
+    /*For each output row*/
+    for(i=top;i<=bottom;i++)
+    {
+        /*Step the derivative buffers*/
+        db_IxIyRow_f(Ix[(i+2)%5],Iy[(i+2)%5],img,(i+2),left-2,chunk_width_p4);
+
+        /*Filter Ix2,IxIy,Iy2 vertically into gxx,gxy,gyy*/
+        db_gxx_gxy_gyy_row_f(gxx,gxy,gyy,chunk_width_p4,
+                                 Ix[(i-2)%5],Ix[(i-1)%5],Ix[i%5],Ix[(i+1)%5],Ix[(i+2)%5],
+                                 Iy[(i-2)%5],Iy[(i-1)%5],Iy[i%5],Iy[(i+1)%5],Iy[(i+2)%5]);
+
+        /*Filter gxx,gxy,gyy horizontally and compute corner response s*/
+        db_HarrisStrength_row_f(s,gxx,gxy,gyy,i,left,chunk_width);
+    }
+}
+
+/*Compute the Harris corner strength of the chunk [left,top,left+123,bottom] of img and
+store it into the corresponding region of s. left and top have to be at least 3 and
+right and bottom have to be at most width-4,height-4. The left of the region in s should
+be 16 byte aligned*/
+inline void db_HarrisStrengthChunk_u(float **s,const unsigned char * const *img,int left,int top,int bottom,
+                                      /*temp should point to at least
+                                      18*128 of allocated memory*/
+                                      int *temp, int nc)
+{
+    int *Ixx[5],*Ixy[5],*Iyy[5];
+    int *gxx,*gxy,*gyy;
+    int i;
+
+    gxx=temp;
+    gxy=gxx+128;
+    gyy=gxy+128;
+    for(i=0;i<5;i++)
+    {
+        Ixx[i]=gyy+(3*i+1)*128;
+        Ixy[i]=gyy+(3*i+2)*128;
+        Iyy[i]=gyy+(3*i+3)*128;
+    }
+
+    /*Fill four rows of the wrap-around derivative buffers*/
+    for(i=top-2;i<top+2;i++) db_IxIyRow_u(Ixx[i%5],img,i,left-2,nc);
+
+    /*For each output row*/
+    for(i=top;i<=bottom;i++)
+    {
+        /*Step the derivative buffers*/
+        db_IxIyRow_u(Ixx[(i+2)%5],img,(i+2),left-2,nc);
+
+        /*Filter Ix2,IxIy,Iy2 vertically into gxx,gxy,gyy*/
+        db_gxx_gxy_gyy_row_s(gxx,Ixx[(i-2)%5],Ixx[(i-1)%5],Ixx[i%5],Ixx[(i+1)%5],Ixx[(i+2)%5],nc);
+
+        /*Filter gxx,gxy,gyy horizontally and compute corner response s*/
+        db_HarrisStrength_row_s(s[i]+left,gxx,gxy,gyy,nc);
+    }
+
+}
+
+/*Compute Harris corner strength of img. Strength is returned for the region
+with (3,3) as upper left and (w-4,h-4) as lower right, positioned in the
+same place in s. In other words,image should be at least 7 pixels wide and 7 pixels high
+for a meaningful result*/
+void db_HarrisStrength_f(float **s,const float * const *img,int w,int h,
+                                    /*temp should point to at least
+                                    13*(chunk_width+4) of allocated memory*/
+                                    float *temp,
+                                    int chunk_width)
+{
+    int x,next_x,last,right;
+
+    last=w-4;
+    for(x=3;x<=last;x=next_x)
+    {
+        next_x=x+chunk_width;
+        right=next_x-1;
+        if(right>last) right=last;
+        /*Compute the Harris strength of a chunk*/
+        db_HarrisStrengthChunk_f(s,img,x,3,right,h-4,temp);
+    }
+}
+
+/*Compute Harris corner strength of img. Strength is returned for the region
+with (3,3) as upper left and (w-4,h-4) as lower right, positioned in the
+same place in s. In other words,image should be at least 7 pixels wide and 7 pixels high
+for a meaningful result.Moreover, the image should be overallocated by 256 bytes.
+s[i][3] should by 16 byte aligned for any i*/
+void db_HarrisStrength_u(float **s, const unsigned char * const *img,int w,int h,
+                                    /*temp should point to at least
+                                    18*128 of allocated memory*/
+                                    int *temp)
+{
+    int x,next_x,last;
+    int nc;
+
+    last=w-4;
+    for(x=3;x<=last;x=next_x)
+    {
+        next_x=x+124;
+
+        // mayban: to revert to the original full chunks state, change the line below to: nc = 128;
+        nc = db_mini(128,last-x+1);
+        //nc = 128;
+
+        /*Compute the Harris strength of a chunk*/
+        db_HarrisStrengthChunk_u(s,img,x,3,h-4,temp,nc);
+    }
+}
+
+inline float db_Max_128Aligned16_f(float *v)
+{
+#ifdef DB_USE_SIMD
+    float back;
+
+    _asm
+    {
+        mov eax,v
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+        movaps xmm2,[eax+32]
+        movaps xmm3,[eax+48]
+        movaps xmm4,[eax+64]
+        movaps xmm5,[eax+80]
+        movaps xmm6,[eax+96]
+        movaps xmm7,[eax+112]
+
+        /*Chunk2*/
+        maxps xmm0,[eax+128]
+        maxps xmm1,[eax+144]
+        maxps xmm2,[eax+160]
+        maxps xmm3,[eax+176]
+        maxps xmm4,[eax+192]
+        maxps xmm5,[eax+208]
+        maxps xmm6,[eax+224]
+        maxps xmm7,[eax+240]
+
+        /*Chunk3*/
+        maxps xmm0,[eax+256]
+        maxps xmm1,[eax+272]
+        maxps xmm2,[eax+288]
+        maxps xmm3,[eax+304]
+        maxps xmm4,[eax+320]
+        maxps xmm5,[eax+336]
+        maxps xmm6,[eax+352]
+        maxps xmm7,[eax+368]
+
+        /*Chunk4*/
+        maxps xmm0,[eax+384]
+        maxps xmm1,[eax+400]
+        maxps xmm2,[eax+416]
+        maxps xmm3,[eax+432]
+        maxps xmm4,[eax+448]
+        maxps xmm5,[eax+464]
+        maxps xmm6,[eax+480]
+        maxps xmm7,[eax+496]
+
+        /*Collect*/
+        maxps   xmm0,xmm1
+        maxps   xmm2,xmm3
+        maxps   xmm4,xmm5
+        maxps   xmm6,xmm7
+        maxps   xmm0,xmm2
+        maxps   xmm4,xmm6
+        maxps   xmm0,xmm4
+        movhlps xmm1,xmm0
+        maxps   xmm0,xmm1
+        shufps  xmm1,xmm0,1
+        maxps   xmm0,xmm1
+        movss   back,xmm0
+    }
+
+    return(back);
+#else
+    float val,max_val;
+    float *p,*stop_p;
+    max_val=v[0];
+    for(p=v+1,stop_p=v+128;p!=stop_p;)
+    {
+        val= *p++;
+        if(val>max_val) max_val=val;
+    }
+    return(max_val);
+#endif /*DB_USE_SIMD*/
+}
+
+inline float db_Max_64Aligned16_f(float *v)
+{
+#ifdef DB_USE_SIMD
+    float back;
+
+    _asm
+    {
+        mov eax,v
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+        movaps xmm2,[eax+32]
+        movaps xmm3,[eax+48]
+        movaps xmm4,[eax+64]
+        movaps xmm5,[eax+80]
+        movaps xmm6,[eax+96]
+        movaps xmm7,[eax+112]
+
+        /*Chunk2*/
+        maxps xmm0,[eax+128]
+        maxps xmm1,[eax+144]
+        maxps xmm2,[eax+160]
+        maxps xmm3,[eax+176]
+        maxps xmm4,[eax+192]
+        maxps xmm5,[eax+208]
+        maxps xmm6,[eax+224]
+        maxps xmm7,[eax+240]
+
+        /*Collect*/
+        maxps   xmm0,xmm1
+        maxps   xmm2,xmm3
+        maxps   xmm4,xmm5
+        maxps   xmm6,xmm7
+        maxps   xmm0,xmm2
+        maxps   xmm4,xmm6
+        maxps   xmm0,xmm4
+        movhlps xmm1,xmm0
+        maxps   xmm0,xmm1
+        shufps  xmm1,xmm0,1
+        maxps   xmm0,xmm1
+        movss   back,xmm0
+    }
+
+    return(back);
+#else
+    float val,max_val;
+    float *p,*stop_p;
+    max_val=v[0];
+    for(p=v+1,stop_p=v+64;p!=stop_p;)
+    {
+        val= *p++;
+        if(val>max_val) max_val=val;
+    }
+    return(max_val);
+#endif /*DB_USE_SIMD*/
+}
+
+inline float db_Max_32Aligned16_f(float *v)
+{
+#ifdef DB_USE_SIMD
+    float back;
+
+    _asm
+    {
+        mov eax,v
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+        movaps xmm2,[eax+32]
+        movaps xmm3,[eax+48]
+        movaps xmm4,[eax+64]
+        movaps xmm5,[eax+80]
+        movaps xmm6,[eax+96]
+        movaps xmm7,[eax+112]
+
+        /*Collect*/
+        maxps   xmm0,xmm1
+        maxps   xmm2,xmm3
+        maxps   xmm4,xmm5
+        maxps   xmm6,xmm7
+        maxps   xmm0,xmm2
+        maxps   xmm4,xmm6
+        maxps   xmm0,xmm4
+        movhlps xmm1,xmm0
+        maxps   xmm0,xmm1
+        shufps  xmm1,xmm0,1
+        maxps   xmm0,xmm1
+        movss   back,xmm0
+    }
+
+    return(back);
+#else
+    float val,max_val;
+    float *p,*stop_p;
+    max_val=v[0];
+    for(p=v+1,stop_p=v+32;p!=stop_p;)
+    {
+        val= *p++;
+        if(val>max_val) max_val=val;
+    }
+    return(max_val);
+#endif /*DB_USE_SIMD*/
+}
+
+inline float db_Max_16Aligned16_f(float *v)
+{
+#ifdef DB_USE_SIMD
+    float back;
+
+    _asm
+    {
+        mov eax,v
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+        movaps xmm2,[eax+32]
+        movaps xmm3,[eax+48]
+
+        /*Collect*/
+        maxps   xmm0,xmm1
+        maxps   xmm2,xmm3
+        maxps   xmm0,xmm2
+        movhlps xmm1,xmm0
+        maxps   xmm0,xmm1
+        shufps  xmm1,xmm0,1
+        maxps   xmm0,xmm1
+        movss   back,xmm0
+    }
+
+    return(back);
+#else
+    float val,max_val;
+    float *p,*stop_p;
+    max_val=v[0];
+    for(p=v+1,stop_p=v+16;p!=stop_p;)
+    {
+        val= *p++;
+        if(val>max_val) max_val=val;
+    }
+    return(max_val);
+#endif /*DB_USE_SIMD*/
+}
+
+inline float db_Max_8Aligned16_f(float *v)
+{
+#ifdef DB_USE_SIMD
+    float back;
+
+    _asm
+    {
+        mov eax,v
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+
+        /*Collect*/
+        maxps   xmm0,xmm1
+        movhlps xmm1,xmm0
+        maxps   xmm0,xmm1
+        shufps  xmm1,xmm0,1
+        maxps   xmm0,xmm1
+        movss   back,xmm0
+    }
+
+    return(back);
+#else
+    float val,max_val;
+    float *p,*stop_p;
+    max_val=v[0];
+    for(p=v+1,stop_p=v+8;p!=stop_p;)
+    {
+        val= *p++;
+        if(val>max_val) max_val=val;
+    }
+    return(max_val);
+#endif /*DB_USE_SIMD*/
+}
+
+inline float db_Max_Aligned16_f(float *v,int size)
+{
+    float val,max_val;
+    float *stop_v;
+
+    max_val=v[0];
+    for(;size>=128;size-=128)
+    {
+        val=db_Max_128Aligned16_f(v);
+        v+=128;
+        if(val>max_val) max_val=val;
+    }
+    if(size&64)
+    {
+        val=db_Max_64Aligned16_f(v);
+        v+=64;
+        if(val>max_val) max_val=val;
+    }
+    if(size&32)
+    {
+        val=db_Max_32Aligned16_f(v);
+        v+=32;
+        if(val>max_val) max_val=val;
+    }
+    if(size&16)
+    {
+        val=db_Max_16Aligned16_f(v);
+        v+=16;
+        if(val>max_val) max_val=val;
+    }
+    if(size&8)
+    {
+        val=db_Max_8Aligned16_f(v);
+        v+=8;
+        if(val>max_val) max_val=val;
+    }
+    if(size&7)
+    {
+        for(stop_v=v+(size&7);v!=stop_v;)
+        {
+            val= *v++;
+            if(val>max_val) max_val=val;
+        }
+    }
+
+    return(max_val);
+}
+
+/*Find maximum value of img in the region starting at (left,top)
+and with width w and height h. img[left] should be 16 byte aligned*/
+float db_MaxImage_Aligned16_f(float **img,int left,int top,int w,int h)
+{
+    float val,max_val;
+    int i,stop_i;
+
+    if(w && h)
+    {
+        stop_i=top+h;
+        max_val=img[top][left];
+
+        for(i=top;i<stop_i;i++)
+        {
+            val=db_Max_Aligned16_f(img[i]+left,w);
+            if(val>max_val) max_val=val;
+        }
+        return(max_val);
+    }
+    return(0.0);
+}
+
+inline void db_MaxVector_128_Aligned16_f(float *m,float *v1,float *v2)
+{
+#ifdef DB_USE_SIMD
+    _asm
+    {
+        mov eax,v1
+        mov ebx,v2
+        mov ecx,m
+
+        /*Chunk1*/
+        movaps xmm0,[eax]
+        movaps xmm1,[eax+16]
+        movaps xmm2,[eax+32]
+        movaps xmm3,[eax+48]
+        movaps xmm4,[eax+64]
+        movaps xmm5,[eax+80]
+        movaps xmm6,[eax+96]
+        movaps xmm7,[eax+112]
+        maxps  xmm0,[ebx]
+        maxps  xmm1,[ebx+16]
+        maxps  xmm2,[ebx+32]
+        maxps  xmm3,[ebx+48]
+        maxps  xmm4,[ebx+64]
+        maxps  xmm5,[ebx+80]
+        maxps  xmm6,[ebx+96]
+        maxps  xmm7,[ebx+112]
+        movaps [ecx],xmm0
+        movaps [ecx+16],xmm1
+        movaps [ecx+32],xmm2
+        movaps [ecx+48],xmm3
+        movaps [ecx+64],xmm4
+        movaps [ecx+80],xmm5
+        movaps [ecx+96],xmm6
+        movaps [ecx+112],xmm7
+
+        /*Chunk2*/
+        movaps xmm0,[eax+128]
+        movaps xmm1,[eax+144]
+        movaps xmm2,[eax+160]
+        movaps xmm3,[eax+176]
+        movaps xmm4,[eax+192]
+        movaps xmm5,[eax+208]
+        movaps xmm6,[eax+224]
+        movaps xmm7,[eax+240]
+        maxps  xmm0,[ebx+128]
+        maxps  xmm1,[ebx+144]
+        maxps  xmm2,[ebx+160]
+        maxps  xmm3,[ebx+176]
+        maxps  xmm4,[ebx+192]
+        maxps  xmm5,[ebx+208]
+        maxps  xmm6,[ebx+224]
+        maxps  xmm7,[ebx+240]
+        movaps [ecx+128],xmm0
+        movaps [ecx+144],xmm1
+        movaps [ecx+160],xmm2
+        movaps [ecx+176],xmm3
+        movaps [ecx+192],xmm4
+        movaps [ecx+208],xmm5
+        movaps [ecx+224],xmm6
+        movaps [ecx+240],xmm7
+
+        /*Chunk3*/
+        movaps xmm0,[eax+256]
+        movaps xmm1,[eax+272]
+        movaps xmm2,[eax+288]
+        movaps xmm3,[eax+304]
+        movaps xmm4,[eax+320]
+        movaps xmm5,[eax+336]
+        movaps xmm6,[eax+352]
+        movaps xmm7,[eax+368]
+        maxps  xmm0,[ebx+256]
+        maxps  xmm1,[ebx+272]
+        maxps  xmm2,[ebx+288]
+        maxps  xmm3,[ebx+304]
+        maxps  xmm4,[ebx+320]
+        maxps  xmm5,[ebx+336]
+        maxps  xmm6,[ebx+352]
+        maxps  xmm7,[ebx+368]
+        movaps [ecx+256],xmm0
+        movaps [ecx+272],xmm1
+        movaps [ecx+288],xmm2
+        movaps [ecx+304],xmm3
+        movaps [ecx+320],xmm4
+        movaps [ecx+336],xmm5
+        movaps [ecx+352],xmm6
+        movaps [ecx+368],xmm7
+
+        /*Chunk4*/
+        movaps xmm0,[eax+384]
+        movaps xmm1,[eax+400]
+        movaps xmm2,[eax+416]
+        movaps xmm3,[eax+432]
+        movaps xmm4,[eax+448]
+        movaps xmm5,[eax+464]
+        movaps xmm6,[eax+480]
+        movaps xmm7,[eax+496]
+        maxps  xmm0,[ebx+384]
+        maxps  xmm1,[ebx+400]
+        maxps  xmm2,[ebx+416]
+        maxps  xmm3,[ebx+432]
+        maxps  xmm4,[ebx+448]
+        maxps  xmm5,[ebx+464]
+        maxps  xmm6,[ebx+480]
+        maxps  xmm7,[ebx+496]
+        movaps [ecx+384],xmm0
+        movaps [ecx+400],xmm1
+        movaps [ecx+416],xmm2
+        movaps [ecx+432],xmm3
+        movaps [ecx+448],xmm4
+        movaps [ecx+464],xmm5
+        movaps [ecx+480],xmm6
+        movaps [ecx+496],xmm7
+    }
+#else
+    int i;
+    float a,b;
+    for(i=0;i<128;i++)
+    {
+        a=v1[i];
+        b=v2[i];
+        if(a>=b) m[i]=a;
+        else m[i]=b;
+    }
+#endif /*DB_USE_SIMD*/
+}
+
+inline void db_MaxVector_128_SecondSourceDestAligned16_f(float *m,float *v1,float *v2)
+{
+#ifdef DB_USE_SIMD
+    _asm
+    {
+        mov eax,v1
+        mov ebx,v2
+        mov ecx,m
+
+        /*Chunk1*/
+        movups xmm0,[eax]
+        movups xmm1,[eax+16]
+        movups xmm2,[eax+32]
+        movups xmm3,[eax+48]
+        movups xmm4,[eax+64]
+        movups xmm5,[eax+80]
+        movups xmm6,[eax+96]
+        movups xmm7,[eax+112]
+        maxps  xmm0,[ebx]
+        maxps  xmm1,[ebx+16]
+        maxps  xmm2,[ebx+32]
+        maxps  xmm3,[ebx+48]
+        maxps  xmm4,[ebx+64]
+        maxps  xmm5,[ebx+80]
+        maxps  xmm6,[ebx+96]
+        maxps  xmm7,[ebx+112]
+        movaps [ecx],xmm0
+        movaps [ecx+16],xmm1
+        movaps [ecx+32],xmm2
+        movaps [ecx+48],xmm3
+        movaps [ecx+64],xmm4
+        movaps [ecx+80],xmm5
+        movaps [ecx+96],xmm6
+        movaps [ecx+112],xmm7
+
+        /*Chunk2*/
+        movups xmm0,[eax+128]
+        movups xmm1,[eax+144]
+        movups xmm2,[eax+160]
+        movups xmm3,[eax+176]
+        movups xmm4,[eax+192]
+        movups xmm5,[eax+208]
+        movups xmm6,[eax+224]
+        movups xmm7,[eax+240]
+        maxps  xmm0,[ebx+128]
+        maxps  xmm1,[ebx+144]
+        maxps  xmm2,[ebx+160]
+        maxps  xmm3,[ebx+176]
+        maxps  xmm4,[ebx+192]
+        maxps  xmm5,[ebx+208]
+        maxps  xmm6,[ebx+224]
+        maxps  xmm7,[ebx+240]
+        movaps [ecx+128],xmm0
+        movaps [ecx+144],xmm1
+        movaps [ecx+160],xmm2
+        movaps [ecx+176],xmm3
+        movaps [ecx+192],xmm4
+        movaps [ecx+208],xmm5
+        movaps [ecx+224],xmm6
+        movaps [ecx+240],xmm7
+
+        /*Chunk3*/
+        movups xmm0,[eax+256]
+        movups xmm1,[eax+272]
+        movups xmm2,[eax+288]
+        movups xmm3,[eax+304]
+        movups xmm4,[eax+320]
+        movups xmm5,[eax+336]
+        movups xmm6,[eax+352]
+        movups xmm7,[eax+368]
+        maxps  xmm0,[ebx+256]
+        maxps  xmm1,[ebx+272]
+        maxps  xmm2,[ebx+288]
+        maxps  xmm3,[ebx+304]
+        maxps  xmm4,[ebx+320]
+        maxps  xmm5,[ebx+336]
+        maxps  xmm6,[ebx+352]
+        maxps  xmm7,[ebx+368]
+        movaps [ecx+256],xmm0
+        movaps [ecx+272],xmm1
+        movaps [ecx+288],xmm2
+        movaps [ecx+304],xmm3
+        movaps [ecx+320],xmm4
+        movaps [ecx+336],xmm5
+        movaps [ecx+352],xmm6
+        movaps [ecx+368],xmm7
+
+        /*Chunk4*/
+        movups xmm0,[eax+384]
+        movups xmm1,[eax+400]
+        movups xmm2,[eax+416]
+        movups xmm3,[eax+432]
+        movups xmm4,[eax+448]
+        movups xmm5,[eax+464]
+        movups xmm6,[eax+480]
+        movups xmm7,[eax+496]
+        maxps  xmm0,[ebx+384]
+        maxps  xmm1,[ebx+400]
+        maxps  xmm2,[ebx+416]
+        maxps  xmm3,[ebx+432]
+        maxps  xmm4,[ebx+448]
+        maxps  xmm5,[ebx+464]
+        maxps  xmm6,[ebx+480]
+        maxps  xmm7,[ebx+496]
+        movaps [ecx+384],xmm0
+        movaps [ecx+400],xmm1
+        movaps [ecx+416],xmm2
+        movaps [ecx+432],xmm3
+        movaps [ecx+448],xmm4
+        movaps [ecx+464],xmm5
+        movaps [ecx+480],xmm6
+        movaps [ecx+496],xmm7
+    }
+#else
+    int i;
+    float a,b;
+    for(i=0;i<128;i++)
+    {
+        a=v1[i];
+        b=v2[i];
+        if(a>=b) m[i]=a;
+        else m[i]=b;
+    }
+#endif /*DB_USE_SIMD*/
+}
+
+/*Compute Max-suppression-filtered image for a chunk of sf starting at (left,top), of width 124 and
+stopping at bottom. The output is shifted two steps left and overwrites 128 elements for each row.
+The input s should be of width at least 128, and exist for 2 pixels outside the specified region.
+s[i][left-2] and sf[i][left-2] should be 16 byte aligned. Top must be at least 3*/
+inline void db_MaxSuppressFilterChunk_5x5_Aligned16_f(float **sf,float **s,int left,int top,int bottom,
+                                      /*temp should point to at least
+                                      6*132 floats of 16-byte-aligned allocated memory*/
+                                      float *temp)
+{
+#ifdef DB_USE_SIMD
+    int i,lm2;
+    float *two[4];
+    float *four,*five;
+
+    lm2=left-2;
+
+    /*Set pointers to pre-allocated memory*/
+    four=temp;
+    five=four+132;
+    for(i=0;i<4;i++)
+    {
+        two[i]=five+(i+1)*132;
+    }
+
+    /*Set rests of four and five to zero to avoid
+    floating point exceptions*/
+    for(i=129;i<132;i++)
+    {
+        four[i]=0.0;
+        five[i]=0.0;
+    }
+
+    /*Fill three rows of the wrap-around max buffers*/
+    for(i=top-3;i<top;i++) db_MaxVector_128_Aligned16_f(two[i&3],s[i+1]+lm2,s[i+2]+lm2);
+
+    /*For each output row*/
+    for(;i<=bottom;i++)
+    {
+        /*Compute max of the lowest pair of rows in the five row window*/
+        db_MaxVector_128_Aligned16_f(two[i&3],s[i+1]+lm2,s[i+2]+lm2);
+        /*Compute max of the lowest and highest pair of rows in the five row window*/
+        db_MaxVector_128_Aligned16_f(four,two[i&3],two[(i-3)&3]);
+        /*Compute max of all rows*/
+        db_MaxVector_128_Aligned16_f(five,four,two[(i-1)&3]);
+        /*Compute max of 2x5 chunks*/
+        db_MaxVector_128_SecondSourceDestAligned16_f(five,five+1,five);
+        /*Compute max of pairs of 2x5 chunks*/
+        db_MaxVector_128_SecondSourceDestAligned16_f(five,five+3,five);
+        /*Compute max of pairs of 5x5 except middle*/
+        db_MaxVector_128_SecondSourceDestAligned16_f(sf[i]+lm2,four+2,five);
+    }
+
+#else
+    int i,j,right;
+    float sv;
+
+    right=left+128;
+    for(i=top;i<=bottom;i++) for(j=left;j<right;j++)
+    {
+        sv=s[i][j];
+
+        if( sv>s[i-2][j-2] && sv>s[i-2][j-1] && sv>s[i-2][j] && sv>s[i-2][j+1] && sv>s[i-2][j+2] &&
+            sv>s[i-1][j-2] && sv>s[i-1][j-1] && sv>s[i-1][j] && sv>s[i-1][j+1] && sv>s[i-1][j+2] &&
+            sv>s[  i][j-2] && sv>s[  i][j-1] &&                 sv>s[  i][j+1] && sv>s[  i][j+2] &&
+            sv>s[i+1][j-2] && sv>s[i+1][j-1] && sv>s[i+1][j] && sv>s[i+1][j+1] && sv>s[i+1][j+2] &&
+            sv>s[i+2][j-2] && sv>s[i+2][j-1] && sv>s[i+2][j] && sv>s[i+2][j+1] && sv>s[i+2][j+2])
+        {
+            sf[i][j-2]=0.0;
+        }
+        else sf[i][j-2]=sv;
+    }
+#endif /*DB_USE_SIMD*/
+}
+
+/*Compute Max-suppression-filtered image for a chunk of sf starting at (left,top) and
+stopping at bottom. The output is shifted two steps left. The input s should exist for 2 pixels
+outside the specified region. s[i][left-2] and sf[i][left-2] should be 16 byte aligned.
+Top must be at least 3. Reading and writing from and to the input and output images is done
+as if the region had a width equal to a multiple of 124. If this is not the case, the images
+should be over-allocated and the input cleared for a sufficient region*/
+void db_MaxSuppressFilter_5x5_Aligned16_f(float **sf,float **s,int left,int top,int right,int bottom,
+                                          /*temp should point to at least
+                                          6*132 floats of 16-byte-aligned allocated memory*/
+                                          float *temp)
+{
+    int x,next_x;
+
+    for(x=left;x<=right;x=next_x)
+    {
+        next_x=x+124;
+        db_MaxSuppressFilterChunk_5x5_Aligned16_f(sf,s,x,top,bottom,temp);
+    }
+}
+
+/*Extract corners from the chunk (left,top) to (right,bottom). Store in x_temp,y_temp and s_temp
+which should point to space of at least as many positions as there are pixels in the chunk*/
+inline int db_CornersFromChunk(float **strength,int left,int top,int right,int bottom,float threshold,double *x_temp,double *y_temp,double *s_temp)
+{
+    int i,j,nr;
+    float s;
+
+    nr=0;
+    for(i=top;i<=bottom;i++) for(j=left;j<=right;j++)
+    {
+        s=strength[i][j];
+
+        if(s>=threshold &&
+            s>strength[i-2][j-2] && s>strength[i-2][j-1] && s>strength[i-2][j] && s>strength[i-2][j+1] && s>strength[i-2][j+2] &&
+            s>strength[i-1][j-2] && s>strength[i-1][j-1] && s>strength[i-1][j] && s>strength[i-1][j+1] && s>strength[i-1][j+2] &&
+            s>strength[  i][j-2] && s>strength[  i][j-1] &&                       s>strength[  i][j+1] && s>strength[  i][j+2] &&
+            s>strength[i+1][j-2] && s>strength[i+1][j-1] && s>strength[i+1][j] && s>strength[i+1][j+1] && s>strength[i+1][j+2] &&
+            s>strength[i+2][j-2] && s>strength[i+2][j-1] && s>strength[i+2][j] && s>strength[i+2][j+1] && s>strength[i+2][j+2])
+        {
+            x_temp[nr]=(double) j;
+            y_temp[nr]=(double) i;
+            s_temp[nr]=(double) s;
+            nr++;
+        }
+    }
+    return(nr);
+}
+
+
+//Sub-pixel accuracy using 2D quadratic interpolation.(YCJ)
+inline void db_SubPixel(float **strength, const double xd, const double yd, double &xs, double &ys)
+{
+    int x = (int) xd;
+    int y = (int) yd;
+
+    float fxx = strength[y][x-1] - strength[y][x] - strength[y][x] + strength[y][x+1];
+    float fyy = strength[y-1][x] - strength[y][x] - strength[y][x] + strength[y+1][x];
+    float fxy = (strength[y-1][x-1] - strength[y-1][x+1] - strength[y+1][x-1] + strength[y+1][x+1])/(float)4.0;
+
+    float denom = (fxx * fyy - fxy * fxy) * (float) 2.0;
+
+    xs = xd;
+    ys = yd;
+
+    if ( db_absf(denom) <= FLT_EPSILON )
+    {
+        return;
+    }
+    else
+    {
+        float fx = strength[y][x+1] - strength[y][x-1];
+        float fy = strength[y+1][x] - strength[y-1][x];
+
+        float dx = (fyy * fx - fxy * fy) / denom;
+        float dy = (fxx * fy - fxy * fx) / denom;
+
+        if ( db_absf(dx) > 1.0 || db_absf(dy) > 1.0 )
+        {
+            return;
+        }
+        else
+        {
+            xs -= dx;
+            ys -= dy;
+        }
+    }
+
+    return;
+}
+
+/*Extract corners from the image part from (left,top) to (right,bottom).
+Store in x and y, extracting at most satnr corners in each block of size (bw,bh).
+The pointer temp_d should point to at least 5*bw*bh positions.
+area_factor holds how many corners max to extract per 10000 pixels*/
+void db_ExtractCornersSaturated(float **strength,int left,int top,int right,int bottom,
+                                int bw,int bh,unsigned long area_factor,
+                                float threshold,double *temp_d,
+                                double *x_coord,double *y_coord,int *nr_corners)
+{
+    double *x_temp,*y_temp,*s_temp,*select_temp;
+    double loc_thresh;
+    unsigned long bwbh,area,saturation;
+    int x,next_x,last_x;
+    int y,next_y,last_y;
+    int nr,nr_points,i,stop;
+
+    bwbh=bw*bh;
+    x_temp=temp_d;
+    y_temp=x_temp+bwbh;
+    s_temp=y_temp+bwbh;
+    select_temp=s_temp+bwbh;
+
+#ifdef DB_SUB_PIXEL
+    // subpixel processing may sometimes push the corner ourside the real border
+    // increasing border size:
+    left++;
+    top++;
+    bottom--;
+    right--;
+#endif /*DB_SUB_PIXEL*/
+
+    nr_points=0;
+    for(y=top;y<=bottom;y=next_y)
+    {
+        next_y=y+bh;
+        last_y=next_y-1;
+        if(last_y>bottom) last_y=bottom;
+        for(x=left;x<=right;x=next_x)
+        {
+            next_x=x+bw;
+            last_x=next_x-1;
+            if(last_x>right) last_x=right;
+
+            area=(last_x-x+1)*(last_y-y+1);
+            saturation=(area*area_factor)/10000;
+            nr=db_CornersFromChunk(strength,x,y,last_x,last_y,threshold,x_temp,y_temp,s_temp);
+            if(nr)
+            {
+                if(((unsigned long)nr)>saturation) loc_thresh=db_LeanQuickSelect(s_temp,nr,nr-saturation,select_temp);
+                else loc_thresh=threshold;
+
+                stop=nr_points+saturation;
+                for(i=0;(i<nr)&&(nr_points<stop);i++)
+                {
+                    if(s_temp[i]>=loc_thresh)
+                    {
+                        #ifdef DB_SUB_PIXEL
+                               db_SubPixel(strength, x_temp[i], y_temp[i], x_coord[nr_points], y_coord[nr_points]);
+                        #else
+                               x_coord[nr_points]=x_temp[i];
+                               y_coord[nr_points]=y_temp[i];
+                        #endif
+
+                        nr_points++;
+                    }
+                }
+            }
+        }
+    }
+    *nr_corners=nr_points;
+}
+
+db_CornerDetector_f::db_CornerDetector_f()
+{
+    m_w=0; m_h=0;
+}
+
+db_CornerDetector_f::~db_CornerDetector_f()
+{
+    Clean();
+}
+
+void db_CornerDetector_f::Clean()
+{
+    if(m_w!=0)
+    {
+        delete [] m_temp_f;
+        delete [] m_temp_d;
+        db_FreeStrengthImage_f(m_strength_mem,m_strength,m_h);
+    }
+    m_w=0; m_h=0;
+}
+
+unsigned long db_CornerDetector_f::Init(int im_width,int im_height,int target_nr_corners,
+                            int nr_horizontal_blocks,int nr_vertical_blocks,
+                            double absolute_threshold,double relative_threshold)
+{
+    int chunkwidth=208;
+    int block_width,block_height;
+    unsigned long area_factor;
+    int active_width,active_height;
+
+    active_width=db_maxi(1,im_width-10);
+    active_height=db_maxi(1,im_height-10);
+    block_width=db_maxi(1,active_width/nr_horizontal_blocks);
+    block_height=db_maxi(1,active_height/nr_vertical_blocks);
+
+    area_factor=db_minl(1000,db_maxl(1,(long)(10000.0*((double)target_nr_corners)/
+        (((double)active_width)*((double)active_height)))));
+
+    return(Start(im_width,im_height,block_width,block_height,area_factor,
+        absolute_threshold,relative_threshold,chunkwidth));
+}
+
+unsigned long db_CornerDetector_f::Start(int im_width,int im_height,
+                             int block_width,int block_height,unsigned long area_factor,
+                             double absolute_threshold,double relative_threshold,int chunkwidth)
+{
+    Clean();
+
+    m_w=im_width;
+    m_h=im_height;
+    m_cw=chunkwidth;
+    m_bw=block_width;
+    m_bh=block_height;
+    m_area_factor=area_factor;
+    m_r_thresh=relative_threshold;
+    m_a_thresh=absolute_threshold;
+    m_max_nr=db_maxl(1,1+(m_w*m_h*m_area_factor)/10000);
+
+    m_temp_f=new float[13*(m_cw+4)];
+    m_temp_d=new double[5*m_bw*m_bh];
+    m_strength=db_AllocStrengthImage_f(&m_strength_mem,m_w,m_h);
+
+    return(m_max_nr);
+}
+
+void db_CornerDetector_f::DetectCorners(const float * const *img,double *x_coord,double *y_coord,int *nr_corners) const
+{
+    float max_val,threshold;
+
+    db_HarrisStrength_f(m_strength,img,m_w,m_h,m_temp_f,m_cw);
+
+    if(m_r_thresh)
+    {
+        max_val=db_MaxImage_Aligned16_f(m_strength,3,3,m_w-6,m_h-6);
+        threshold= (float) db_maxd(m_a_thresh,max_val*m_r_thresh);
+    }
+    else threshold= (float) m_a_thresh;
+
+    db_ExtractCornersSaturated(m_strength,BORDER,BORDER,m_w-BORDER-1,m_h-BORDER-1,m_bw,m_bh,m_area_factor,threshold,
+        m_temp_d,x_coord,y_coord,nr_corners);
+}
+
+db_CornerDetector_u::db_CornerDetector_u()
+{
+    m_w=0; m_h=0;
+}
+
+db_CornerDetector_u::~db_CornerDetector_u()
+{
+    Clean();
+}
+
+db_CornerDetector_u::db_CornerDetector_u(const db_CornerDetector_u& cd)
+{
+    Start(cd.m_w, cd.m_h, cd.m_bw, cd.m_bh, cd.m_area_factor,
+        cd.m_a_thresh, cd.m_r_thresh);
+}
+
+db_CornerDetector_u& db_CornerDetector_u::operator=(const db_CornerDetector_u& cd)
+{
+    if ( this == &cd ) return *this;
+
+    Clean();
+
+    Start(cd.m_w, cd.m_h, cd.m_bw, cd.m_bh, cd.m_area_factor,
+        cd.m_a_thresh, cd.m_r_thresh);
+
+    return *this;
+}
+
+void db_CornerDetector_u::Clean()
+{
+    if(m_w!=0)
+    {
+        delete [] m_temp_i;
+        delete [] m_temp_d;
+        db_FreeStrengthImage_f(m_strength_mem,m_strength,m_h);
+    }
+    m_w=0; m_h=0;
+}
+
+unsigned long db_CornerDetector_u::Init(int im_width,int im_height,int target_nr_corners,
+                            int nr_horizontal_blocks,int nr_vertical_blocks,
+                            double absolute_threshold,double relative_threshold)
+{
+    int block_width,block_height;
+    unsigned long area_factor;
+    int active_width,active_height;
+
+    active_width=db_maxi(1,im_width-10);
+    active_height=db_maxi(1,im_height-10);
+    block_width=db_maxi(1,active_width/nr_horizontal_blocks);
+    block_height=db_maxi(1,active_height/nr_vertical_blocks);
+
+    area_factor=db_minl(1000,db_maxl(1,(long)(10000.0*((double)target_nr_corners)/
+        (((double)active_width)*((double)active_height)))));
+
+    return(Start(im_width,im_height,block_width,block_height,area_factor,
+        16.0*absolute_threshold,relative_threshold));
+}
+
+unsigned long db_CornerDetector_u::Start(int im_width,int im_height,
+                             int block_width,int block_height,unsigned long area_factor,
+                             double absolute_threshold,double relative_threshold)
+{
+    Clean();
+
+    m_w=im_width;
+    m_h=im_height;
+    m_bw=block_width;
+    m_bh=block_height;
+    m_area_factor=area_factor;
+    m_r_thresh=relative_threshold;
+    m_a_thresh=absolute_threshold;
+    m_max_nr=db_maxl(1,1+(m_w*m_h*m_area_factor)/10000);
+
+    m_temp_i=new int[18*128];
+    m_temp_d=new double[5*m_bw*m_bh];
+    m_strength=db_AllocStrengthImage_f(&m_strength_mem,m_w,m_h);
+
+    return(m_max_nr);
+}
+
+void db_CornerDetector_u::DetectCorners(const unsigned char * const *img,double *x_coord,double *y_coord,int *nr_corners,
+                                        const unsigned char * const *msk, unsigned char fgnd) const
+{
+    float max_val,threshold;
+
+    db_HarrisStrength_u(m_strength,img,m_w,m_h,m_temp_i);
+
+
+    if(m_r_thresh)
+    {
+        max_val=db_MaxImage_Aligned16_f(m_strength,3,3,m_w-6,m_h-6);
+        threshold= (float) db_maxd(m_a_thresh,max_val*m_r_thresh);
+    }
+    else threshold= (float) m_a_thresh;
+
+    db_ExtractCornersSaturated(m_strength,BORDER,BORDER,m_w-BORDER-1,m_h-BORDER-1,m_bw,m_bh,m_area_factor,threshold,
+        m_temp_d,x_coord,y_coord,nr_corners);
+
+
+    if ( msk )
+    {
+        int nr_corners_mask=0;
+
+        for ( int i = 0; i < *nr_corners; ++i)
+        {
+            int cor_x = db_roundi(*(x_coord+i));
+            int cor_y = db_roundi(*(y_coord+i));
+            if ( msk[cor_y][cor_x] == fgnd )
+            {
+                x_coord[nr_corners_mask] = x_coord[i];
+                y_coord[nr_corners_mask] = y_coord[i];
+                nr_corners_mask++;
+            }
+        }
+        *nr_corners = nr_corners_mask;
+    }
+}
+
+void db_CornerDetector_u::ExtractCorners(float ** strength, double *x_coord, double *y_coord, int *nr_corners) {
+    if ( m_w!=0 )
+        db_ExtractCornersSaturated(strength,BORDER,BORDER,m_w-BORDER-1,m_h-BORDER-1,m_bw,m_bh,m_area_factor,float(m_a_thresh),
+            m_temp_d,x_coord,y_coord,nr_corners);
+}
+
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.h b/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.h
new file mode 100644
index 0000000..68ffcc9
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_feature_detection.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*$Id: db_feature_detection.h,v 1.3 2011/06/17 14:03:30 mbansal Exp $*/
+
+#ifndef DB_FEATURE_DETECTION_H
+#define DB_FEATURE_DETECTION_H
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup FeatureDetection Feature Detection
+ */
+#include "db_utilities.h"
+#include "db_utilities_constants.h"
+#include <stdlib.h> //for NULL
+
+/*!
+ * \class db_CornerDetector_f
+ * \ingroup FeatureDetection
+ * \brief Harris corner detector for float images.
+ *
+ *  This class performs Harris corner extraction on *float* images managed
+ * with functions in \ref LMImageBasicUtilities.
+ */
+class DB_API db_CornerDetector_f
+{
+public:
+    db_CornerDetector_f();
+    ~db_CornerDetector_f();
+
+    /*!
+     * Set parameters and pre-allocate memory. Return an upper bound
+     * on the number of corners detected in one frame.
+     * \param im_width      width
+     * \param im_height     height
+     * \param target_nr_corners
+     * \param nr_horizontal_blocks
+     * \param nr_vertical_blocks
+     * \param absolute_threshold
+     * \param relative_threshold
+     */
+    unsigned long Init(int im_width,int im_height,
+                            int target_nr_corners=DB_DEFAULT_TARGET_NR_CORNERS,
+                            int nr_horizontal_blocks=DB_DEFAULT_NR_FEATURE_BLOCKS,
+                            int nr_vertical_blocks=DB_DEFAULT_NR_FEATURE_BLOCKS,
+                            double absolute_threshold=DB_DEFAULT_ABS_CORNER_THRESHOLD,
+                            double relative_threshold=DB_DEFAULT_REL_CORNER_THRESHOLD);
+
+    /*!
+     * Detect the corners.
+     * x_coord and y_coord should be pre-allocated arrays of length returned by Init().
+     * \param img   row array pointer
+     * \param x_coord   corner locations
+     * \param y_coord   corner locations
+     * \param nr_corners    actual number of corners computed
+     */
+    void DetectCorners(const float * const *img,double *x_coord,double *y_coord,int *nr_corners) const;
+    void SetAbsoluteThreshold(double a_thresh) { m_a_thresh = a_thresh; };
+    void SetRelativeThreshold(double r_thresh) { m_r_thresh = r_thresh; };
+protected:
+    void Clean();
+    unsigned long Start(int im_width,int im_height,
+            int block_width,int block_height,unsigned long area_factor,
+            double absolute_threshold,double relative_threshold,int chunkwidth);
+
+    int m_w,m_h,m_cw,m_bw,m_bh;
+    /*Area factor holds the maximum number of corners to detect
+    per 10000 pixels*/
+    unsigned long m_area_factor,m_max_nr;
+    double m_a_thresh,m_r_thresh;
+    float *m_temp_f;
+    double *m_temp_d;
+    float **m_strength,*m_strength_mem;
+};
+/*!
+ * \class db_CornerDetector_u
+ * \ingroup FeatureDetection
+ * \brief Harris corner detector for byte images.
+ *
+ *  This class performs Harris corner extraction on *byte* images managed
+ * with functions in \ref LMImageBasicUtilities.
+ */
+class DB_API db_CornerDetector_u
+{
+public:
+    db_CornerDetector_u();
+    virtual ~db_CornerDetector_u();
+
+    /*!
+     Copy ctor duplicates settings.
+     Memory is not copied.
+     */
+    db_CornerDetector_u(const db_CornerDetector_u& cd);
+    /*!
+     Assignment optor duplicates settings.
+     Memory not copied.
+     */
+    db_CornerDetector_u& operator=(const db_CornerDetector_u& cd);
+
+    /*!
+     * Set parameters and pre-allocate memory. Return an upper bound
+     * on the number of corners detected in one frame
+     */
+    virtual unsigned long Init(int im_width,int im_height,
+                            int target_nr_corners=DB_DEFAULT_TARGET_NR_CORNERS,
+                            int nr_horizontal_blocks=DB_DEFAULT_NR_FEATURE_BLOCKS,
+                            int nr_vertical_blocks=DB_DEFAULT_NR_FEATURE_BLOCKS,
+                            double absolute_threshold=DB_DEFAULT_ABS_CORNER_THRESHOLD,
+                            double relative_threshold=DB_DEFAULT_REL_CORNER_THRESHOLD);
+
+    /*!
+     * Detect the corners.
+     * Observe that the image should be overallocated by at least 256 bytes
+     * at the end.
+     * x_coord and y_coord should be pre-allocated arrays of length returned by Init().
+     * Specifying image mask will restrict corner output to foreground regions.
+     * Foreground value can be specified using fgnd. By default any >0 mask value
+     * is considered to be foreground
+     * \param img   row array pointer
+     * \param x_coord   corner locations
+     * \param y_coord   corner locations
+     * \param nr_corners    actual number of corners computed
+     * \param msk       row array pointer to mask image
+     * \param fgnd      foreground value in the mask
+     */
+    virtual void DetectCorners(const unsigned char * const *img,double *x_coord,double *y_coord,int *nr_corners,
+        const unsigned char * const * msk=NULL, unsigned char fgnd=255) const;
+
+    /*!
+     Set absolute feature threshold
+     */
+    virtual void SetAbsoluteThreshold(double a_thresh) { m_a_thresh = a_thresh; };
+    /*!
+     Set relative feature threshold
+     */
+    virtual void SetRelativeThreshold(double r_thresh) { m_r_thresh = r_thresh; };
+
+    /*!
+     Extract corners from a pre-computed strength image.
+     \param strength    Harris strength image
+     \param x_coord corner locations
+     \param y_coord corner locations
+     \param nr_corners  actual number of corners computed
+     */
+    virtual void ExtractCorners(float ** strength, double *x_coord, double *y_coord, int *nr_corners);
+protected:
+    virtual void Clean();
+    /*The absolute threshold to this function should be 16.0 times
+    normal*/
+    unsigned long Start(int im_width,int im_height,
+            int block_width,int block_height,unsigned long area_factor,
+            double absolute_threshold,double relative_threshold);
+
+    int m_w,m_h,m_bw,m_bh;
+    /*Area factor holds the maximum number of corners to detect
+    per 10000 pixels*/
+    unsigned long m_area_factor,m_max_nr;
+    double m_a_thresh,m_r_thresh;
+    int *m_temp_i;
+    double *m_temp_d;
+    float **m_strength,*m_strength_mem;
+};
+
+#endif /*DB_FEATURE_DETECTION_H*/
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.cpp b/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.cpp
new file mode 100644
index 0000000..d278d0c
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.cpp
@@ -0,0 +1,3410 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*$Id: db_feature_matching.cpp,v 1.4 2011/06/17 14:03:30 mbansal Exp $*/
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_utilities.h"
+#include "db_feature_matching.h"
+#ifdef _VERBOSE_
+#include <iostream>
+#endif
+
+
+int AffineWarpPoint_NN_LUT_x[11][11];
+int AffineWarpPoint_NN_LUT_y[11][11];
+
+float AffineWarpPoint_BL_LUT_x[11][11];
+float AffineWarpPoint_BL_LUT_y[11][11];
+
+
+inline float db_SignedSquareNormCorr7x7_u(unsigned char **f_img,unsigned char **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    unsigned char *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-3;
+    xm_g=x_g-3;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=49.0f*fgsum-fsum*gsum;
+    den=(49.0f*f2sum-fsum*fsum)*(49.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline float db_SignedSquareNormCorr9x9_u(unsigned char **f_img,unsigned char **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    unsigned char *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-4;
+    xm_g=x_g-4;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=81.0f*fgsum-fsum*gsum;
+    den=(81.0f*f2sum-fsum*fsum)*(81.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline float db_SignedSquareNormCorr11x11_u(unsigned char **f_img,unsigned char **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    unsigned char *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-5;
+    xm_g=x_g-5;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=121.0f*fgsum-fsum*gsum;
+    den=(121.0f*f2sum-fsum*fsum)*(121.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline void db_SignedSquareNormCorr11x11_Pre_u(unsigned char **f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    unsigned char *pf;
+    float den;
+    int f,f2sum,fsum;
+    int xm_f;
+
+    xm_f=x_f-5;
+
+    pf=f_img[y_f-5]+xm_f;
+    f= *pf++; f2sum=f*f;  fsum=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+5]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    *sum= (float) fsum;
+    den=(121.0f*f2sum-fsum*fsum);
+    *recip=(float)(((den!=0.0)?1.0/den:0.0));
+}
+
+inline void db_SignedSquareNormCorr5x5_PreAlign_u(short *patch,const unsigned char * const *f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    float den;
+    int f2sum,fsum;
+    int xm_f=x_f-2;
+
+#ifndef DB_USE_SSE2
+    const unsigned char *pf;
+    short f;
+
+    pf=f_img[y_f-2]+xm_f;
+    f= *pf++; f2sum=f*f; fsum=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+    //int xwi;
+    //int ywi;
+    //f2sum=0;
+    //fsum=0;
+    //for (int r=-5;r<=5;r++){
+    //  ywi=y_f+r;
+    //  for (int c=-5;c<=5;c++){
+    //      xwi=x_f+c;
+    //      f=f_img[ywi][xwi];
+    //      f2sum+=f*f;
+    //      fsum+=f;
+    //      (*patch++)=f;
+    //  }
+    //}
+    (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0;
+    (*patch++)=0; (*patch++)=0;
+#endif /* DB_USE_SSE2 */
+
+    *sum= (float) fsum;
+    den=(25.0f*f2sum-fsum*fsum);
+    *recip= (float)((den!=0.0)?1.0/den:0.0);
+}
+
+inline void db_SignedSquareNormCorr21x21_PreAlign_u(short *patch,const unsigned char * const *f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    float den;
+    int f2sum,fsum;
+    int xm_f=x_f-10;
+    short f;
+
+    int xwi;
+    int ywi;
+    f2sum=0;
+    fsum=0;
+    for (int r=-10;r<=10;r++){
+        ywi=y_f+r;
+        for (int c=-10;c<=10;c++){
+            xwi=x_f+c;
+            f=f_img[ywi][xwi];
+            f2sum+=f*f;
+            fsum+=f;
+            (*patch++)=f;
+        }
+    }
+
+    for(int i=442; i<512; i++)
+        (*patch++)=0;
+
+    *sum= (float) fsum;
+    den=(441.0f*f2sum-fsum*fsum);
+    *recip= (float)((den!=0.0)?1.0/den:0.0);
+
+
+}
+
+/* Lay out the image in the patch, computing norm and
+*/
+inline void db_SignedSquareNormCorr11x11_PreAlign_u(short *patch,const unsigned char * const *f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    float den;
+    int f2sum,fsum;
+    int xm_f=x_f-5;
+
+#ifndef DB_USE_SSE2
+    const unsigned char *pf;
+    short f;
+
+    pf=f_img[y_f-5]+xm_f;
+    f= *pf++; f2sum=f*f;  fsum=f;  (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+5]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    //int xwi;
+    //int ywi;
+    //f2sum=0;
+    //fsum=0;
+    //for (int r=-5;r<=5;r++){
+    //  ywi=y_f+r;
+    //  for (int c=-5;c<=5;c++){
+    //      xwi=x_f+c;
+    //      f=f_img[ywi][xwi];
+    //      f2sum+=f*f;
+    //      fsum+=f;
+    //      (*patch++)=f;
+    //  }
+    //}
+
+    (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0;
+    (*patch++)=0; (*patch++)=0;
+#else
+    const unsigned char *pf0 =f_img[y_f-5]+xm_f;
+    const unsigned char *pf1 =f_img[y_f-4]+xm_f;
+    const unsigned char *pf2 =f_img[y_f-3]+xm_f;
+    const unsigned char *pf3 =f_img[y_f-2]+xm_f;
+    const unsigned char *pf4 =f_img[y_f-1]+xm_f;
+    const unsigned char *pf5 =f_img[y_f  ]+xm_f;
+    const unsigned char *pf6 =f_img[y_f+1]+xm_f;
+    const unsigned char *pf7 =f_img[y_f+2]+xm_f;
+    const unsigned char *pf8 =f_img[y_f+3]+xm_f;
+    const unsigned char *pf9 =f_img[y_f+4]+xm_f;
+    const unsigned char *pf10=f_img[y_f+5]+xm_f;
+
+    /* pixel mask */
+    const unsigned char pm[16] = {
+        0xFF,0xFF,
+        0xFF,0xFF,
+        0xFF,0xFF,
+        0,0,0,0,0,
+        0,0,0,0,0};
+    const unsigned char * pm_p = pm;
+
+    _asm
+    {
+        mov         ecx,patch   /* load patch pointer */
+        mov         ebx, pm_p   /* load pixel mask pointer */
+        movdqu      xmm1,[ebx]  /* load pixel mask */
+
+        pxor        xmm5,xmm5   /* set xmm5 to 0 accumulator for sum squares */
+        pxor        xmm4,xmm4   /* set xmm4 to 0 accumulator for sum */
+        pxor        xmm0,xmm0   /* set xmm0 to 0 */
+
+        /* row 0 */
+        mov         eax,pf0     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqa      [ecx+0*22],xmm7 /* move short values to patch */
+        movdqa      [ecx+0*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 1 */
+        mov         eax,pf1     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+1*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+1*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 2 */
+        mov         eax,pf2     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+2*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+2*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 3 */
+        mov         eax,pf3     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+3*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+3*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 4 */
+        mov         eax,pf4     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+4*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+4*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 5 */
+        mov         eax,pf5     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+5*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+5*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 6 */
+        mov         eax,pf6     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+6*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+6*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 7 */
+        mov         eax,pf7     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+7*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+7*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 8 */
+        mov         eax,pf8     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqa      [ecx+8*22],xmm7 /* move short values to patch */
+        movdqa      [ecx+8*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 9 */
+        mov         eax,pf9     /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+9*22],xmm7 /* move short values to patch */
+        movdqu      [ecx+9*22+16],xmm6  /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit uints into 16 bit uints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* row 10 */
+        mov         eax,pf10    /* load image pointer */
+        movdqu      xmm7,[eax]  /* load 16 pixels */
+        movdqa      xmm6,xmm7
+
+        punpcklbw   xmm7,xmm0   /* unpack low pixels (first 8)*/
+        punpckhbw   xmm6,xmm0   /* unpack high pixels (last 8)*/
+
+        pand        xmm6,xmm1   /* mask out pixels 12-16 */
+
+        movdqu      [ecx+10*22],xmm7    /* move short values to patch */
+        movdqu      [ecx+10*22+16],xmm6 /* move short values to patch */
+
+        paddusw     xmm4,xmm7   /* accumulate sums */
+        pmaddwd     xmm7,xmm7   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm7   /* accumulate sum squares */
+
+        paddw       xmm4,xmm6   /* accumulate sums */
+        pmaddwd     xmm6,xmm6   /* multiply 16 bit ints and add into 32 bit ints */
+        paddd       xmm5,xmm6   /* accumulate sum squares */
+
+        /* add up the sum squares */
+        movhlps     xmm0,xmm5   /* high half to low half */
+        paddd       xmm5,xmm0   /* add high to low */
+        pshuflw     xmm0,xmm5, 0xE /* reshuffle */
+        paddd       xmm5,xmm0   /* add remaining */
+        movd        f2sum,xmm5
+
+        /* add up the sum */
+        movhlps     xmm0,xmm4
+        paddw       xmm4,xmm0   /* halves added */
+        pshuflw     xmm0,xmm4,0xE
+        paddw       xmm4,xmm0   /* quarters added */
+        pshuflw     xmm0,xmm4,0x1
+        paddw       xmm4,xmm0   /* eighth added */
+        movd        fsum, xmm4
+
+        emms
+    }
+
+    fsum = fsum & 0xFFFF;
+
+    patch[126] = 0;
+    patch[127] = 0;
+#endif /* DB_USE_SSE2 */
+
+    *sum= (float) fsum;
+    den=(121.0f*f2sum-fsum*fsum);
+    *recip= (float)((den!=0.0)?1.0/den:0.0);
+}
+
+void AffineWarpPointOffset(float &r_w,float &c_w,double Hinv[9],int r,int c)
+{
+    r_w=(float)(Hinv[3]*c+Hinv[4]*r);
+    c_w=(float)(Hinv[0]*c+Hinv[1]*r);
+}
+
+
+
+/*!
+Prewarp the patches with given affine transform. For a given homogeneous point "x", "H*x" is
+the warped point and for any displacement "d" in the warped image resulting in point "y", the
+corresponding point in the original image is given by "Hinv*y", which can be simplified for affine H.
+If "affine" is 1, then nearest neighbor method is used, else if it is 2, then
+bilinear method is used.
+ */
+inline void db_SignedSquareNormCorr11x11_PreAlign_AffinePatchWarp_u(short *patch,const unsigned char * const *f_img,
+                                                                    int xi,int yi,float *sum,float *recip,
+                                                                    const double Hinv[9],int affine)
+{
+    float den;
+    short f;
+    int f2sum,fsum;
+
+    f2sum=0;
+    fsum=0;
+
+    if (affine==1)
+    {
+        for (int r=0;r<11;r++){
+            for (int c=0;c<11;c++){
+                f=f_img[yi+AffineWarpPoint_NN_LUT_y[r][c]][xi+AffineWarpPoint_NN_LUT_x[r][c]];
+                f2sum+=f*f;
+                fsum+=f;
+                (*patch++)=f;
+            }
+        }
+    }
+    else if (affine==2)
+    {
+        for (int r=0;r<11;r++){
+            for (int c=0;c<11;c++){
+                f=db_BilinearInterpolation(yi+AffineWarpPoint_BL_LUT_y[r][c]
+                ,xi+AffineWarpPoint_BL_LUT_x[r][c],f_img);
+                f2sum+=f*f;
+                fsum+=f;
+                (*patch++)=f;
+            }
+        }
+    }
+
+
+
+    (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0; (*patch++)=0;
+    (*patch++)=0; (*patch++)=0;
+
+    *sum= (float) fsum;
+    den=(121.0f*f2sum-fsum*fsum);
+    *recip= (float)((den!=0.0)?1.0/den:0.0);
+}
+
+
+inline float db_SignedSquareNormCorr11x11_Post_u(unsigned char **f_img,unsigned char **g_img,int x_f,int y_f,int x_g,int y_g,
+                                                float fsum_gsum,float f_recip_g_recip)
+{
+    unsigned char *pf,*pg;
+    int fgsum;
+    float fg_corr;
+    int xm_f,xm_g;
+
+    xm_f=x_f-5;
+    xm_g=x_g-5;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    fgsum=(*pf++)*(*pg++);  fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    fg_corr=121.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+float db_SignedSquareNormCorr21x21Aligned_Post_s(const short *f_patch,const short *g_patch,float fsum_gsum,float f_recip_g_recip)
+{
+    float fgsum,fg_corr;
+
+    fgsum= (float) db_ScalarProduct512_s(f_patch,g_patch);
+
+    fg_corr=441.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+
+float db_SignedSquareNormCorr11x11Aligned_Post_s(const short *f_patch,const short *g_patch,float fsum_gsum,float f_recip_g_recip)
+{
+    float fgsum,fg_corr;
+
+    fgsum= (float) db_ScalarProduct128_s(f_patch,g_patch);
+
+    fg_corr=121.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+float db_SignedSquareNormCorr5x5Aligned_Post_s(const short *f_patch,const short *g_patch,float fsum_gsum,float f_recip_g_recip)
+{
+    float fgsum,fg_corr;
+
+    fgsum= (float) db_ScalarProduct32_s(f_patch,g_patch);
+
+    fg_corr=25.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+
+inline float db_SignedSquareNormCorr15x15_u(unsigned char **f_img,unsigned char **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    unsigned char *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-7;
+    xm_g=x_g-7;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-7]+xm_f; pg=g_img[y_g-7]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-6]+xm_f; pg=g_img[y_g-6]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+6]+xm_f; pg=g_img[y_g+6]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+7]+xm_f; pg=g_img[y_g+7]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=225.0f*fgsum-fsum*gsum;
+    den=(225.0f*f2sum-fsum*fsum)*(225.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline float db_SignedSquareNormCorr7x7_f(float **f_img,float **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    float f,g,*pf,*pg,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-3;
+    xm_g=x_g-3;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=49.0f*fgsum-fsum*gsum;
+    den=(49.0f*f2sum-fsum*fsum)*(49.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline float db_SignedSquareNormCorr9x9_f(float **f_img,float **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    float f,g,*pf,*pg,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-4;
+    xm_g=x_g-4;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=81.0f*fgsum-fsum*gsum;
+    den=(81.0f*f2sum-fsum*fsum)*(81.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline float db_SignedSquareNormCorr11x11_f(float **f_img,float **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    float *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-5;
+    xm_g=x_g-5;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=121.0f*fgsum-fsum*gsum;
+    den=(121.0f*f2sum-fsum*fsum)*(121.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+inline void db_SignedSquareNormCorr11x11_Pre_f(float **f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    float *pf,den;
+    float f,f2sum,fsum;
+    int xm_f;
+
+    xm_f=x_f-5;
+
+    pf=f_img[y_f-5]+xm_f;
+    f= *pf++; f2sum=f*f;  fsum=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f-1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    pf=f_img[y_f+5]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf++; f2sum+=f*f; fsum+=f;
+    f= *pf;   f2sum+=f*f; fsum+=f;
+
+    *sum=fsum;
+    den=(121.0f*f2sum-fsum*fsum);
+    *recip= (float) ((den!=0.0)?1.0/den:0.0);
+}
+
+inline void db_SignedSquareNormCorr11x11_PreAlign_f(float *patch,const float * const *f_img,int x_f,int y_f,float *sum,float *recip)
+{
+    const float *pf;
+    float den,f,f2sum,fsum;
+    int xm_f;
+
+    xm_f=x_f-5;
+
+    pf=f_img[y_f-5]+xm_f;
+    f= *pf++; f2sum=f*f;  fsum=f;  (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f-1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+1]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+2]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+3]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+4]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    pf=f_img[y_f+5]+xm_f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf++; f2sum+=f*f; fsum+=f; (*patch++)=f;
+    f= *pf;   f2sum+=f*f; fsum+=f; (*patch++)=f;
+
+    (*patch++)=0.0; (*patch++)=0.0; (*patch++)=0.0; (*patch++)=0.0; (*patch++)=0.0;
+    (*patch++)=0.0; (*patch++)=0.0;
+
+    *sum=fsum;
+    den=(121.0f*f2sum-fsum*fsum);
+    *recip= (float) ((den!=0.0)?1.0/den:0.0);
+}
+
+inline float db_SignedSquareNormCorr11x11_Post_f(float **f_img,float **g_img,int x_f,int y_f,int x_g,int y_g,
+                                                float fsum_gsum,float f_recip_g_recip)
+{
+    float *pf,*pg;
+    float fgsum,fg_corr;
+    int xm_f,xm_g;
+
+    xm_f=x_f-5;
+    xm_g=x_g-5;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    fgsum=(*pf++)*(*pg++);  fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++); fgsum+=(*pf++)*(*pg++);
+    fgsum+=(*pf++)*(*pg++); fgsum+=(*pf)*(*pg);
+
+    fg_corr=121.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+inline float db_SignedSquareNormCorr11x11Aligned_Post_f(const float *f_patch,const float *g_patch,float fsum_gsum,float f_recip_g_recip)
+{
+    float fgsum,fg_corr;
+
+    fgsum=db_ScalarProduct128Aligned16_f(f_patch,g_patch);
+
+    fg_corr=121.0f*fgsum-fsum_gsum;
+    if(fg_corr>=0.0) return(fg_corr*fg_corr*f_recip_g_recip);
+    return(-fg_corr*fg_corr*f_recip_g_recip);
+}
+
+inline float db_SignedSquareNormCorr15x15_f(float **f_img,float **g_img,int x_f,int y_f,int x_g,int y_g)
+{
+    float *pf,*pg;
+    float f,g,fgsum,f2sum,g2sum,fsum,gsum,fg_corr,den;
+    int xm_f,xm_g;
+
+    xm_f=x_f-7;
+    xm_g=x_g-7;
+    fgsum=0.0; f2sum=0.0; g2sum=0.0; fsum=0.0; gsum=0.0;
+
+    pf=f_img[y_f-7]+xm_f; pg=g_img[y_g-7]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-6]+xm_f; pg=g_img[y_g-6]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-5]+xm_f; pg=g_img[y_g-5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-4]+xm_f; pg=g_img[y_g-4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-3]+xm_f; pg=g_img[y_g-3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-2]+xm_f; pg=g_img[y_g-2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f-1]+xm_f; pg=g_img[y_g-1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f]+xm_f; pg=g_img[y_g]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+1]+xm_f; pg=g_img[y_g+1]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+2]+xm_f; pg=g_img[y_g+2]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+3]+xm_f; pg=g_img[y_g+3]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+4]+xm_f; pg=g_img[y_g+4]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+5]+xm_f; pg=g_img[y_g+5]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+6]+xm_f; pg=g_img[y_g+6]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    pf=f_img[y_f+7]+xm_f; pg=g_img[y_g+7]+xm_g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf++; g= *pg++; fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+    f= *pf;   g= *pg;   fgsum+=f*g; f2sum+=f*f; g2sum+=g*g; fsum+=f; gsum+=g;
+
+    fg_corr=225.0f*fgsum-fsum*gsum;
+    den=(225.0f*f2sum-fsum*fsum)*(225.0f*g2sum-gsum*gsum);
+    if(den!=0.0)
+    {
+        if(fg_corr>=0.0) return(fg_corr*fg_corr/den);
+        return(-fg_corr*fg_corr/den);
+    }
+    return(0.0);
+}
+
+db_Bucket_f** db_AllocBuckets_f(int nr_h,int nr_v,int bd)
+{
+    int i,j;
+    db_Bucket_f **bp,*b;
+
+    b=new db_Bucket_f [(nr_h+2)*(nr_v+2)];
+    bp=new db_Bucket_f* [(nr_v+2)];
+    bp=bp+1;
+    for(i= -1;i<=nr_v;i++)
+    {
+        bp[i]=b+1+(nr_h+2)*(i+1);
+        for(j= -1;j<=nr_h;j++)
+        {
+            bp[i][j].ptr=new db_PointInfo_f [bd];
+        }
+    }
+
+    return(bp);
+}
+
+db_Bucket_u** db_AllocBuckets_u(int nr_h,int nr_v,int bd)
+{
+    int i,j;
+    db_Bucket_u **bp,*b;
+
+    b=new db_Bucket_u [(nr_h+2)*(nr_v+2)];
+    bp=new db_Bucket_u* [(nr_v+2)];
+    bp=bp+1;
+    for(i= -1;i<=nr_v;i++)
+    {
+        bp[i]=b+1+(nr_h+2)*(i+1);
+        for(j= -1;j<=nr_h;j++)
+        {
+            bp[i][j].ptr=new db_PointInfo_u [bd];
+        }
+    }
+
+    return(bp);
+}
+
+void db_FreeBuckets_f(db_Bucket_f **bp,int nr_h,int nr_v)
+{
+    int i,j;
+
+    for(i= -1;i<=nr_v;i++) for(j= -1;j<=nr_h;j++)
+    {
+        delete [] bp[i][j].ptr;
+    }
+    delete [] (bp[-1]-1);
+    delete [] (bp-1);
+}
+
+void db_FreeBuckets_u(db_Bucket_u **bp,int nr_h,int nr_v)
+{
+    int i,j;
+
+    for(i= -1;i<=nr_v;i++) for(j= -1;j<=nr_h;j++)
+    {
+        delete [] bp[i][j].ptr;
+    }
+    delete [] (bp[-1]-1);
+    delete [] (bp-1);
+}
+
+void db_EmptyBuckets_f(db_Bucket_f **bp,int nr_h,int nr_v)
+{
+    int i,j;
+    for(i= -1;i<=nr_v;i++) for(j= -1;j<=nr_h;j++) bp[i][j].nr=0;
+}
+
+void db_EmptyBuckets_u(db_Bucket_u **bp,int nr_h,int nr_v)
+{
+    int i,j;
+    for(i= -1;i<=nr_v;i++) for(j= -1;j<=nr_h;j++) bp[i][j].nr=0;
+}
+
+float* db_FillBuckets_f(float *patch_space,const float * const *f_img,db_Bucket_f **bp,int bw,int bh,int nr_h,int nr_v,int bd,const double *x,const double *y,int nr_corners)
+{
+    int i,xi,yi,xpos,ypos,nr;
+    db_Bucket_f *br;
+    db_PointInfo_f *pir;
+
+    db_EmptyBuckets_f(bp,nr_h,nr_v);
+    for(i=0;i<nr_corners;i++)
+    {
+        xi=(int) x[i];
+        yi=(int) y[i];
+        xpos=xi/bw;
+        ypos=yi/bh;
+        if(xpos>=0 && xpos<nr_h && ypos>=0 && ypos<nr_v)
+        {
+            br=&bp[ypos][xpos];
+            nr=br->nr;
+            if(nr<bd)
+            {
+                pir=&(br->ptr[nr]);
+                pir->x=xi;
+                pir->y=yi;
+                pir->id=i;
+                pir->pir=0;
+                pir->patch=patch_space;
+                br->nr=nr+1;
+
+                db_SignedSquareNormCorr11x11_PreAlign_f(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                patch_space+=128;
+            }
+        }
+    }
+    return(patch_space);
+}
+
+short* db_FillBuckets_u(short *patch_space,const unsigned char * const *f_img,db_Bucket_u **bp,int bw,int bh,int nr_h,int nr_v,int bd,const double *x,const double *y,int nr_corners,int use_smaller_matching_window, int use_21)
+{
+    int i,xi,yi,xpos,ypos,nr;
+    db_Bucket_u *br;
+    db_PointInfo_u *pir;
+
+    db_EmptyBuckets_u(bp,nr_h,nr_v);
+    for(i=0;i<nr_corners;i++)
+    {
+        xi=(int)db_roundi(x[i]);
+        yi=(int)db_roundi(y[i]);
+        xpos=xi/bw;
+        ypos=yi/bh;
+        if(xpos>=0 && xpos<nr_h && ypos>=0 && ypos<nr_v)
+        {
+            br=&bp[ypos][xpos];
+            nr=br->nr;
+            if(nr<bd)
+            {
+                pir=&(br->ptr[nr]);
+                pir->x=xi;
+                pir->y=yi;
+                pir->id=i;
+                pir->pir=0;
+                pir->patch=patch_space;
+                br->nr=nr+1;
+
+                if(use_21)
+                {
+                    db_SignedSquareNormCorr21x21_PreAlign_u(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                    patch_space+=512;
+                }
+                else
+                {
+                if(!use_smaller_matching_window)
+                {
+                    db_SignedSquareNormCorr11x11_PreAlign_u(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                    patch_space+=128;
+                }
+                else
+                {
+                    db_SignedSquareNormCorr5x5_PreAlign_u(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                    patch_space+=32;
+                }
+                }
+            }
+        }
+    }
+    return(patch_space);
+}
+
+
+
+float* db_FillBucketsPrewarped_f(float *patch_space,const float *const *f_img,db_Bucket_f **bp,int bw,int bh,int nr_h,int nr_v,int bd,const double *x,const double *y,int nr_corners,const double H[9])
+{
+    int i,xi,yi,xpos,ypos,nr,wxi,wyi;
+    db_Bucket_f *br;
+    db_PointInfo_f *pir;
+    double xd[2],wx[2];
+
+    db_EmptyBuckets_f(bp,nr_h,nr_v);
+    for(i=0;i<nr_corners;i++)
+    {
+        xd[0]=x[i];
+        xd[1]=y[i];
+        xi=(int) xd[0];
+        yi=(int) xd[1];
+        db_ImageHomographyInhomogenous(wx,H,xd);
+        wxi=(int) wx[0];
+        wyi=(int) wx[1];
+
+        xpos=((wxi+bw)/bw)-1;
+        ypos=((wyi+bh)/bh)-1;
+        if(xpos>= -1 && xpos<=nr_h && ypos>= -1 && ypos<=nr_v)
+        {
+            br=&bp[ypos][xpos];
+            nr=br->nr;
+            if(nr<bd)
+            {
+                pir=&(br->ptr[nr]);
+                pir->x=wxi;
+                pir->y=wyi;
+                pir->id=i;
+                pir->pir=0;
+                pir->patch=patch_space;
+                br->nr=nr+1;
+
+                db_SignedSquareNormCorr11x11_PreAlign_f(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                patch_space+=128;
+            }
+        }
+    }
+    return(patch_space);
+}
+
+short* db_FillBucketsPrewarped_u(short *patch_space,const unsigned char * const *f_img,db_Bucket_u **bp,
+                                 int bw,int bh,int nr_h,int nr_v,int bd,const double *x,const double *y,
+                                 int nr_corners,const double H[9])
+{
+    int i,xi,yi,xpos,ypos,nr,wxi,wyi;
+    db_Bucket_u *br;
+    db_PointInfo_u *pir;
+    double xd[2],wx[2];
+
+    db_EmptyBuckets_u(bp,nr_h,nr_v);
+    for(i=0;i<nr_corners;i++)
+    {
+        xd[0]=x[i];
+        xd[1]=y[i];
+        xi=(int) db_roundi(xd[0]);
+        yi=(int) db_roundi(xd[1]);
+        db_ImageHomographyInhomogenous(wx,H,xd);
+        wxi=(int) wx[0];
+        wyi=(int) wx[1];
+
+        xpos=((wxi+bw)/bw)-1;
+        ypos=((wyi+bh)/bh)-1;
+        if(xpos>= -1 && xpos<=nr_h && ypos>= -1 && ypos<=nr_v)
+        {
+            br=&bp[ypos][xpos];
+            nr=br->nr;
+            if(nr<bd)
+            {
+                pir=&(br->ptr[nr]);
+                pir->x=wxi;
+                pir->y=wyi;
+                pir->id=i;
+                pir->pir=0;
+                pir->patch=patch_space;
+                br->nr=nr+1;
+
+                db_SignedSquareNormCorr11x11_PreAlign_u(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip));
+                patch_space+=128;
+            }
+        }
+    }
+    return(patch_space);
+}
+
+
+
+short* db_FillBucketsPrewarpedAffine_u(short *patch_space,const unsigned char * const *f_img,db_Bucket_u **bp,
+                                 int bw,int bh,int nr_h,int nr_v,int bd,const double *x,const double *y,
+                                 int nr_corners,const double H[9],const double Hinv[9],const int warpboundsp[4],
+                                 int affine)
+{
+    int i,xi,yi,xpos,ypos,nr,wxi,wyi;
+    db_Bucket_u *br;
+    db_PointInfo_u *pir;
+    double xd[2],wx[2];
+
+    db_EmptyBuckets_u(bp,nr_h,nr_v);
+    for(i=0;i<nr_corners;i++)
+    {
+        xd[0]=x[i];
+        xd[1]=y[i];
+        xi=(int) db_roundi(xd[0]);
+        yi=(int) db_roundi(xd[1]);
+        db_ImageHomographyInhomogenous(wx,H,xd);
+        wxi=(int) wx[0];
+        wyi=(int) wx[1];
+
+        xpos=((wxi+bw)/bw)-1;
+        ypos=((wyi+bh)/bh)-1;
+
+
+        if (xpos>= -1 && xpos<=nr_h && ypos>= -1 && ypos<=nr_v)
+        {
+            if( xi>warpboundsp[0] && xi<warpboundsp[1] && yi>warpboundsp[2] && yi<warpboundsp[3])
+            {
+
+                br=&bp[ypos][xpos];
+                nr=br->nr;
+                if(nr<bd)
+                {
+                    pir=&(br->ptr[nr]);
+                    pir->x=wxi;
+                    pir->y=wyi;
+                    pir->id=i;
+                    pir->pir=0;
+                    pir->patch=patch_space;
+                    br->nr=nr+1;
+
+                    db_SignedSquareNormCorr11x11_PreAlign_AffinePatchWarp_u(patch_space,f_img,xi,yi,&(pir->sum),&(pir->recip),Hinv,affine);
+                    patch_space+=128;
+                }
+            }
+        }
+    }
+    return(patch_space);
+}
+
+
+
+inline void db_MatchPointPair_f(db_PointInfo_f *pir_l,db_PointInfo_f *pir_r,
+                            unsigned long kA,unsigned long kB)
+{
+    int x_l,y_l,x_r,y_r,xm,ym;
+    double score;
+
+    x_l=pir_l->x;
+    y_l=pir_l->y;
+    x_r=pir_r->x;
+    y_r=pir_r->y;
+    xm=x_l-x_r;
+    ym=y_l-y_r;
+    /*Check if disparity is within the maximum disparity
+    with the formula xm^2*256+ym^2*kA<kB
+    where kA=256*w^2/h^2
+    and   kB=256*max_disp^2*w^2*/
+    if(((xm*xm)<<8)+ym*ym*kA<kB)
+    {
+        /*Correlate*/
+        score=db_SignedSquareNormCorr11x11Aligned_Post_f(pir_l->patch,pir_r->patch,
+            (pir_l->sum)*(pir_r->sum),
+            (pir_l->recip)*(pir_r->recip));
+
+        if((!(pir_l->pir)) || (score>pir_l->s))
+        {
+            /*Update left corner*/
+            pir_l->s=score;
+            pir_l->pir=pir_r;
+        }
+        if((!(pir_r->pir)) || (score>pir_r->s))
+        {
+            /*Update right corner*/
+            pir_r->s=score;
+            pir_r->pir=pir_l;
+        }
+    }
+}
+
+inline void db_MatchPointPair_u(db_PointInfo_u *pir_l,db_PointInfo_u *pir_r,
+                            unsigned long kA,unsigned long kB, unsigned int rect_window,bool use_smaller_matching_window, int use_21)
+{
+    int xm,ym;
+    double score;
+    bool compute_score;
+
+
+    if( rect_window )
+        compute_score = ((unsigned)db_absi(pir_l->x - pir_r->x)<kA && (unsigned)db_absi(pir_l->y - pir_r->y)<kB);
+    else
+    {   /*Check if disparity is within the maximum disparity
+        with the formula xm^2*256+ym^2*kA<kB
+        where kA=256*w^2/h^2
+        and   kB=256*max_disp^2*w^2*/
+        xm= pir_l->x - pir_r->x;
+        ym= pir_l->y - pir_r->y;
+        compute_score = ((xm*xm)<<8)+ym*ym*kA < kB;
+    }
+
+    if ( compute_score )
+    {
+        if(use_21)
+        {
+            score=db_SignedSquareNormCorr21x21Aligned_Post_s(pir_l->patch,pir_r->patch,
+                (pir_l->sum)*(pir_r->sum),
+                (pir_l->recip)*(pir_r->recip));
+        }
+        else
+        {
+        /*Correlate*/
+        if(!use_smaller_matching_window)
+        {
+            score=db_SignedSquareNormCorr11x11Aligned_Post_s(pir_l->patch,pir_r->patch,
+                (pir_l->sum)*(pir_r->sum),
+                (pir_l->recip)*(pir_r->recip));
+        }
+        else
+        {
+            score=db_SignedSquareNormCorr5x5Aligned_Post_s(pir_l->patch,pir_r->patch,
+                (pir_l->sum)*(pir_r->sum),
+                (pir_l->recip)*(pir_r->recip));
+        }
+        }
+
+        if((!(pir_l->pir)) || (score>pir_l->s))
+        {
+            /*Update left corner*/
+            pir_l->s=score;
+            pir_l->pir=pir_r;
+        }
+        if((!(pir_r->pir)) || (score>pir_r->s))
+        {
+            /*Update right corner*/
+            pir_r->s=score;
+            pir_r->pir=pir_l;
+        }
+    }
+}
+
+inline void db_MatchPointAgainstBucket_f(db_PointInfo_f *pir_l,db_Bucket_f *b_r,
+                                       unsigned long kA,unsigned long kB)
+{
+    int p_r,nr;
+    db_PointInfo_f *pir_r;
+
+    nr=b_r->nr;
+    pir_r=b_r->ptr;
+    for(p_r=0;p_r<nr;p_r++) db_MatchPointPair_f(pir_l,pir_r+p_r,kA,kB);
+}
+
+inline void db_MatchPointAgainstBucket_u(db_PointInfo_u *pir_l,db_Bucket_u *b_r,
+                                       unsigned long kA,unsigned long kB,int rect_window, bool use_smaller_matching_window, int use_21)
+{
+    int p_r,nr;
+    db_PointInfo_u *pir_r;
+
+    nr=b_r->nr;
+    pir_r=b_r->ptr;
+
+    for(p_r=0;p_r<nr;p_r++) db_MatchPointPair_u(pir_l,pir_r+p_r,kA,kB, rect_window, use_smaller_matching_window, use_21);
+
+}
+
+void db_MatchBuckets_f(db_Bucket_f **bp_l,db_Bucket_f **bp_r,int nr_h,int nr_v,
+                     unsigned long kA,unsigned long kB)
+{
+    int i,j,k,a,b,br_nr;
+    db_Bucket_f *br;
+    db_PointInfo_f *pir_l;
+
+    /*For all buckets*/
+    for(i=0;i<nr_v;i++) for(j=0;j<nr_h;j++)
+    {
+        br=&bp_l[i][j];
+        br_nr=br->nr;
+        /*For all points in bucket*/
+        for(k=0;k<br_nr;k++)
+        {
+            pir_l=br->ptr+k;
+            for(a=i-1;a<=i+1;a++)
+            {
+                for(b=j-1;b<=j+1;b++)
+                {
+                    db_MatchPointAgainstBucket_f(pir_l,&bp_r[a][b],kA,kB);
+                }
+            }
+        }
+    }
+}
+
+void db_MatchBuckets_u(db_Bucket_u **bp_l,db_Bucket_u **bp_r,int nr_h,int nr_v,
+                     unsigned long kA,unsigned long kB,int rect_window,bool use_smaller_matching_window, int use_21)
+{
+    int i,j,k,a,b,br_nr;
+    db_Bucket_u *br;
+    db_PointInfo_u *pir_l;
+
+    /*For all buckets*/
+    for(i=0;i<nr_v;i++) for(j=0;j<nr_h;j++)
+    {
+        br=&bp_l[i][j];
+        br_nr=br->nr;
+        /*For all points in bucket*/
+        for(k=0;k<br_nr;k++)
+        {
+            pir_l=br->ptr+k;
+            for(a=i-1;a<=i+1;a++)
+            {
+                for(b=j-1;b<=j+1;b++)
+                {
+                    db_MatchPointAgainstBucket_u(pir_l,&bp_r[a][b],kA,kB,rect_window,use_smaller_matching_window, use_21);
+                }
+            }
+        }
+    }
+}
+
+void db_CollectMatches_f(db_Bucket_f **bp_l,int nr_h,int nr_v,unsigned long target,int *id_l,int *id_r,int *nr_matches)
+{
+    int i,j,k,br_nr;
+    unsigned long count;
+    db_Bucket_f *br;
+    db_PointInfo_f *pir,*pir2;
+
+    count=0;
+    /*For all buckets*/
+    for(i=0;i<nr_v;i++) for(j=0;j<nr_h;j++)
+    {
+        br=&bp_l[i][j];
+        br_nr=br->nr;
+        /*For all points in bucket*/
+        for(k=0;k<br_nr;k++)
+        {
+            pir=br->ptr+k;
+            pir2=pir->pir;
+            if(pir2)
+            {
+                /*This point has a best match*/
+                if((pir2->pir)==pir)
+                {
+                    /*We have a mutually consistent match*/
+                    if(count<target)
+                    {
+                        id_l[count]=pir->id;
+                        id_r[count]=pir2->id;
+                        count++;
+                    }
+                }
+            }
+        }
+    }
+    *nr_matches=count;
+}
+
+void db_CollectMatches_u(db_Bucket_u **bp_l,int nr_h,int nr_v,unsigned long target,int *id_l,int *id_r,int *nr_matches)
+{
+    int i,j,k,br_nr;
+    unsigned long count;
+    db_Bucket_u *br;
+    db_PointInfo_u *pir,*pir2;
+
+    count=0;
+    /*For all buckets*/
+    for(i=0;i<nr_v;i++) for(j=0;j<nr_h;j++)
+    {
+        br=&bp_l[i][j];
+        br_nr=br->nr;
+        /*For all points in bucket*/
+        for(k=0;k<br_nr;k++)
+        {
+            pir=br->ptr+k;
+            pir2=pir->pir;
+            if(pir2)
+            {
+                /*This point has a best match*/
+                if((pir2->pir)==pir)
+                {
+                    /*We have a mutually consistent match*/
+                    if(count<target)
+                    {
+                        id_l[count]=pir->id;
+                        id_r[count]=pir2->id;
+                        count++;
+                    }
+                }
+            }
+        }
+    }
+    *nr_matches=count;
+}
+
+db_Matcher_f::db_Matcher_f()
+{
+    m_w=0; m_h=0;
+}
+
+db_Matcher_f::~db_Matcher_f()
+{
+    Clean();
+}
+
+void db_Matcher_f::Clean()
+{
+    if(m_w)
+    {
+        /*Free buckets*/
+        db_FreeBuckets_f(m_bp_l,m_nr_h,m_nr_v);
+        db_FreeBuckets_f(m_bp_r,m_nr_h,m_nr_v);
+        /*Free space for patch layouts*/
+        delete [] m_patch_space;
+    }
+    m_w=0; m_h=0;
+}
+
+unsigned long db_Matcher_f::Init(int im_width,int im_height,double max_disparity,int target_nr_corners)
+{
+    Clean();
+    m_w=im_width;
+    m_h=im_height;
+    m_bw=db_maxi(1,(int) (max_disparity*((double)im_width)));
+    m_bh=db_maxi(1,(int) (max_disparity*((double)im_height)));
+    m_nr_h=1+(im_width-1)/m_bw;
+    m_nr_v=1+(im_height-1)/m_bh;
+    m_bd=db_maxi(1,(int)(((double)target_nr_corners)*
+        max_disparity*max_disparity));
+    m_target=target_nr_corners;
+    m_kA=(long)(256.0*((double)(m_w*m_w))/((double)(m_h*m_h)));
+    m_kB=(long)(256.0*max_disparity*max_disparity*((double)(m_w*m_w)));
+
+    /*Alloc bucket structure*/
+    m_bp_l=db_AllocBuckets_f(m_nr_h,m_nr_v,m_bd);
+    m_bp_r=db_AllocBuckets_f(m_nr_h,m_nr_v,m_bd);
+
+    /*Alloc 16byte-aligned space for patch layouts*/
+    m_patch_space=new float [2*(m_nr_h+2)*(m_nr_v+2)*m_bd*128+16];
+    m_aligned_patch_space=db_AlignPointer_f(m_patch_space,16);
+
+    return(m_target);
+}
+
+void db_Matcher_f::Match(const float * const *l_img,const float * const *r_img,
+        const double *x_l,const double *y_l,int nr_l,const double *x_r,const double *y_r,int nr_r,
+        int *id_l,int *id_r,int *nr_matches,const double H[9])
+{
+    float *ps;
+
+    /*Insert the corners into bucket structure*/
+    ps=db_FillBuckets_f(m_aligned_patch_space,l_img,m_bp_l,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_l,y_l,nr_l);
+    if(H==0) db_FillBuckets_f(ps,r_img,m_bp_r,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_r,y_r,nr_r);
+    else db_FillBucketsPrewarped_f(ps,r_img,m_bp_r,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_r,y_r,nr_r,H);
+
+    /*Compute all the necessary match scores*/
+    db_MatchBuckets_f(m_bp_l,m_bp_r,m_nr_h,m_nr_v,m_kA,m_kB);
+
+    /*Collect the correspondences*/
+    db_CollectMatches_f(m_bp_l,m_nr_h,m_nr_v,m_target,id_l,id_r,nr_matches);
+}
+
+db_Matcher_u::db_Matcher_u()
+{
+    m_w=0; m_h=0;
+    m_rect_window = 0;
+    m_bw=m_bh=m_nr_h=m_nr_v=m_bd=m_target=0;
+    m_bp_l=m_bp_r=0;
+    m_patch_space=m_aligned_patch_space=0;
+}
+
+db_Matcher_u::db_Matcher_u(const db_Matcher_u& cm)
+{
+    Init(cm.m_w, cm.m_h, cm.m_max_disparity, cm.m_target, cm.m_max_disparity_v);
+}
+
+db_Matcher_u& db_Matcher_u::operator= (const db_Matcher_u& cm)
+{
+    if ( this == &cm ) return *this;
+    Init(cm.m_w, cm.m_h, cm.m_max_disparity, cm.m_target, cm.m_max_disparity_v);
+    return *this;
+}
+
+
+db_Matcher_u::~db_Matcher_u()
+{
+    Clean();
+}
+
+void db_Matcher_u::Clean()
+{
+    if(m_w)
+    {
+        /*Free buckets*/
+        db_FreeBuckets_u(m_bp_l,m_nr_h,m_nr_v);
+        db_FreeBuckets_u(m_bp_r,m_nr_h,m_nr_v);
+        /*Free space for patch layouts*/
+        delete [] m_patch_space;
+    }
+    m_w=0; m_h=0;
+}
+
+
+unsigned long db_Matcher_u::Init(int im_width,int im_height,double max_disparity,int target_nr_corners,
+                                 double max_disparity_v, bool use_smaller_matching_window, int use_21)
+{
+    Clean();
+    m_w=im_width;
+    m_h=im_height;
+    m_max_disparity=max_disparity;
+    m_max_disparity_v=max_disparity_v;
+
+    if ( max_disparity_v != DB_DEFAULT_NO_DISPARITY )
+    {
+        m_rect_window = 1;
+
+        m_bw=db_maxi(1,(int)(max_disparity*((double)im_width)));
+        m_bh=db_maxi(1,(int)(max_disparity_v*((double)im_height)));
+
+        m_bd=db_maxi(1,(int)(((double)target_nr_corners)*max_disparity*max_disparity_v));
+
+        m_kA=(int)(max_disparity*m_w);
+        m_kB=(int)(max_disparity_v*m_h);
+
+    } else
+    {
+        m_bw=(int)db_maxi(1,(int)(max_disparity*((double)im_width)));
+        m_bh=(int)db_maxi(1,(int)(max_disparity*((double)im_height)));
+
+        m_bd=db_maxi(1,(int)(((double)target_nr_corners)*max_disparity*max_disparity));
+
+        m_kA=(long)(256.0*((double)(m_w*m_w))/((double)(m_h*m_h)));
+        m_kB=(long)(256.0*max_disparity*max_disparity*((double)(m_w*m_w)));
+    }
+
+    m_nr_h=1+(im_width-1)/m_bw;
+    m_nr_v=1+(im_height-1)/m_bh;
+
+    m_target=target_nr_corners;
+
+    /*Alloc bucket structure*/
+    m_bp_l=db_AllocBuckets_u(m_nr_h,m_nr_v,m_bd);
+    m_bp_r=db_AllocBuckets_u(m_nr_h,m_nr_v,m_bd);
+
+    m_use_smaller_matching_window = use_smaller_matching_window;
+    m_use_21 = use_21;
+
+    if(m_use_21)
+    {
+        /*Alloc 64byte-aligned space for patch layouts*/
+        m_patch_space=new short [2*(m_nr_h+2)*(m_nr_v+2)*m_bd*512+64];
+        m_aligned_patch_space=db_AlignPointer_s(m_patch_space,64);
+    }
+    else
+    {
+    if(!m_use_smaller_matching_window)
+    {
+        /*Alloc 16byte-aligned space for patch layouts*/
+        m_patch_space=new short [2*(m_nr_h+2)*(m_nr_v+2)*m_bd*128+16];
+        m_aligned_patch_space=db_AlignPointer_s(m_patch_space,16);
+    }
+    else
+    {
+        /*Alloc 4byte-aligned space for patch layouts*/
+        m_patch_space=new short [2*(m_nr_h+2)*(m_nr_v+2)*m_bd*32+4];
+        m_aligned_patch_space=db_AlignPointer_s(m_patch_space,4);
+    }
+    }
+
+    return(m_target);
+}
+
+void db_Matcher_u::Match(const unsigned char * const *l_img,const unsigned char * const *r_img,
+        const double *x_l,const double *y_l,int nr_l,const double *x_r,const double *y_r,int nr_r,
+        int *id_l,int *id_r,int *nr_matches,const double H[9],int affine)
+{
+    short *ps;
+
+    /*Insert the corners into bucket structure*/
+    ps=db_FillBuckets_u(m_aligned_patch_space,l_img,m_bp_l,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_l,y_l,nr_l,m_use_smaller_matching_window,m_use_21);
+    if(H==0)
+        db_FillBuckets_u(ps,r_img,m_bp_r,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_r,y_r,nr_r,m_use_smaller_matching_window,m_use_21);
+    else
+    {
+        if (affine)
+        {
+            double Hinv[9];
+            db_InvertAffineTransform(Hinv,H);
+            float r_w, c_w;
+            float stretch_x[2];
+            float stretch_y[2];
+            AffineWarpPointOffset(r_w,c_w,Hinv, 5,5);
+            stretch_x[0]=db_absf(c_w);stretch_y[0]=db_absf(r_w);
+            AffineWarpPointOffset(r_w,c_w,Hinv, 5,-5);
+            stretch_x[1]=db_absf(c_w);stretch_y[1]=db_absf(r_w);
+            int max_stretxh_x=(int) (db_maxd(stretch_x[0],stretch_x[1]));
+            int max_stretxh_y=(int) (db_maxd(stretch_y[0],stretch_y[1]));
+            int warpbounds[4]={max_stretxh_x,m_w-1-max_stretxh_x,max_stretxh_y,m_h-1-max_stretxh_y};
+
+            for (int r=-5;r<=5;r++){
+                for (int c=-5;c<=5;c++){
+                    AffineWarpPointOffset(r_w,c_w,Hinv,r,c);
+                    AffineWarpPoint_BL_LUT_y[r+5][c+5]=r_w;
+                    AffineWarpPoint_BL_LUT_x[r+5][c+5]=c_w;
+
+                    AffineWarpPoint_NN_LUT_y[r+5][c+5]=db_roundi(r_w);
+                    AffineWarpPoint_NN_LUT_x[r+5][c+5]=db_roundi(c_w);
+
+                }
+            }
+
+            db_FillBucketsPrewarpedAffine_u(ps,r_img,m_bp_r,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,
+                x_r,y_r,nr_r,H,Hinv,warpbounds,affine);
+        }
+        else
+            db_FillBucketsPrewarped_u(ps,r_img,m_bp_r,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,x_r,y_r,nr_r,H);
+    }
+
+
+    /*Compute all the necessary match scores*/
+    db_MatchBuckets_u(m_bp_l,m_bp_r,m_nr_h,m_nr_v,m_kA,m_kB, m_rect_window,m_use_smaller_matching_window,m_use_21);
+
+    /*Collect the correspondences*/
+    db_CollectMatches_u(m_bp_l,m_nr_h,m_nr_v,m_target,id_l,id_r,nr_matches);
+}
+
+int db_Matcher_u::IsAllocated()
+{
+    return (int)(m_w != 0);
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.h b/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.h
new file mode 100644
index 0000000..6c056b9
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_feature_matching.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*$Id: db_feature_matching.h,v 1.3 2011/06/17 14:03:30 mbansal Exp $*/
+
+#ifndef DB_FEATURE_MATCHING_H
+#define DB_FEATURE_MATCHING_H
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup FeatureMatching Feature Matching
+ */
+#include "db_utilities.h"
+#include "db_utilities_constants.h"
+
+DB_API void db_SignedSquareNormCorr21x21_PreAlign_u(short *patch,const unsigned char * const *f_img,int x_f,int y_f,float *sum,float *recip);
+DB_API void db_SignedSquareNormCorr11x11_PreAlign_u(short *patch,const unsigned char * const *f_img,int x_f,int y_f,float *sum,float *recip);
+float db_SignedSquareNormCorr21x21Aligned_Post_s(const short *f_patch,const short *g_patch,float fsum_gsum,float f_recip_g_recip);
+float db_SignedSquareNormCorr11x11Aligned_Post_s(const short *f_patch,const short *g_patch,float fsum_gsum,float f_recip_g_recip);
+
+class db_PointInfo_f
+{
+public:
+    /*Coordinates of point*/
+    int x;
+    int y;
+    /*Id nr of point*/
+    int id;
+    /*Best match score*/
+    double s;
+    /*Best match candidate*/
+    db_PointInfo_f *pir;
+    /*Precomputed coefficients
+    of image patch*/
+    float sum;
+    float recip;
+    /*Pointer to patch layout*/
+    const float *patch;
+};
+
+class db_Bucket_f
+{
+public:
+    db_PointInfo_f *ptr;
+    int nr;
+};
+
+class db_PointInfo_u
+{
+public:
+    /*Coordinates of point*/
+    int x;
+    int y;
+    /*Id nr of point*/
+    int id;
+    /*Best match score*/
+    double s;
+    /*Best match candidate*/
+    db_PointInfo_u *pir;
+    /*Precomputed coefficients
+    of image patch*/
+    float sum;
+    float recip;
+    /*Pointer to patch layout*/
+    const short *patch;
+};
+
+class db_Bucket_u
+{
+public:
+    db_PointInfo_u *ptr;
+    int nr;
+};
+/*!
+ * \class db_Matcher_f
+ * \ingroup FeatureMatching
+ * \brief Feature matcher for float images.
+ *
+ * Normalized correlation feature matcher for <b>float</b> images.
+ * Correlation window size is constant and set to 11x11.
+ * See \ref FeatureDetection to detect Harris corners.
+ * Images are managed with functions in \ref LMImageBasicUtilities.
+ */
+class DB_API db_Matcher_f
+{
+public:
+    db_Matcher_f();
+    ~db_Matcher_f();
+
+    /*!
+     * Set parameters and pre-allocate memory. Return an upper bound
+     * on the number of matches.
+     * \param im_width          width
+     * \param im_height         height
+     * \param max_disparity     maximum distance (as fraction of image size) between matches
+     * \param target_nr_corners maximum number of matches
+     * \return maximum number of matches
+     */
+    unsigned long Init(int im_width,int im_height,
+        double max_disparity=DB_DEFAULT_MAX_DISPARITY,
+        int target_nr_corners=DB_DEFAULT_TARGET_NR_CORNERS);
+
+    /*!
+     * Match two sets of features.
+     * If the prewarp H is not NULL it will be applied to the features
+     * in the right image before matching.
+     * Parameters id_l and id_r must point to arrays of size target_nr_corners
+     * (returned by Init()).
+     * The results of matching are in id_l and id_r.
+     * Interpretaqtion of results: if id_l[i] = m and id_r[i] = n,
+     * feature at (x_l[m],y_l[m]) matched to (x_r[n],y_r[n]).
+     * \param l_img     left image
+     * \param r_img     right image
+     * \param x_l       left x coordinates of features
+     * \param y_l       left y coordinates of features
+     * \param nr_l      number of features in left image
+     * \param x_r       right x coordinates of features
+     * \param y_r       right y coordinates of features
+     * \param nr_r      number of features in right image
+     * \param id_l      indices of left features that matched
+     * \param id_r      indices of right features that matched
+     * \param nr_matches    number of features actually matched
+     * \param H         image homography (prewarp) to be applied to right image features
+     */
+    void Match(const float * const *l_img,const float * const *r_img,
+        const double *x_l,const double *y_l,int nr_l,const double *x_r,const double *y_r,int nr_r,
+        int *id_l,int *id_r,int *nr_matches,const double H[9]=0);
+
+protected:
+    void Clean();
+
+    int m_w,m_h,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,m_target;
+    unsigned long m_kA,m_kB;
+    db_Bucket_f **m_bp_l;
+    db_Bucket_f **m_bp_r;
+    float *m_patch_space,*m_aligned_patch_space;
+};
+/*!
+ * \class db_Matcher_u
+ * \ingroup FeatureMatching
+ * \brief Feature matcher for byte images.
+ *
+ * Normalized correlation feature matcher for <b>byte</b> images.
+ * Correlation window size is constant and set to 11x11.
+ * See \ref FeatureDetection to detect Harris corners.
+ * Images are managed with functions in \ref LMImageBasicUtilities.
+ *
+ * If the prewarp matrix H is supplied, the feature coordinates are warped by H before being placed in
+ * appropriate buckets. If H is an affine transform and the "affine" parameter is set to 1 or 2,
+ * then the correlation patches themselves are warped before being placed in the patch space.
+ */
+class DB_API db_Matcher_u
+{
+public:
+    db_Matcher_u();
+
+    int GetPatchSize(){return 11;};
+
+    virtual ~db_Matcher_u();
+
+    /*!
+     Copy ctor duplicates settings.
+     Memory not copied.
+     */
+    db_Matcher_u(const db_Matcher_u& cm);
+
+    /*!
+     Assignment optor duplicates settings
+     Memory not copied.
+     */
+    db_Matcher_u& operator= (const db_Matcher_u& cm);
+
+    /*!
+     * Set parameters and pre-allocate memory. Return an upper bound
+     * on the number of matches.
+     * If max_disparity_v is DB_DEFAULT_NO_DISPARITY, look for matches
+     * in a ellipse around a feature of radius max_disparity*im_width by max_disparity*im_height.
+     * If max_disparity_v is specified, use a rectangle max_disparity*im_width by max_disparity_v*im_height.
+     * \param im_width          width
+     * \param im_height         height
+     * \param max_disparity     maximum distance (as fraction of image size) between matches
+     * \param target_nr_corners maximum number of matches
+     * \param max_disparity_v   maximum vertical disparity (distance between matches)
+     * \param use_smaller_matching_window   if set to true, uses a correlation window of 5x5 instead of the default 11x11
+     * \return maximum number of matches
+     */
+    virtual unsigned long Init(int im_width,int im_height,
+        double max_disparity=DB_DEFAULT_MAX_DISPARITY,
+        int target_nr_corners=DB_DEFAULT_TARGET_NR_CORNERS,
+        double max_disparity_v=DB_DEFAULT_NO_DISPARITY,
+        bool use_smaller_matching_window=false, int use_21=0);
+
+    /*!
+     * Match two sets of features.
+     * If the prewarp H is not NULL it will be applied to the features
+     * in the right image before matching.
+     * Parameters id_l and id_r must point to arrays of size target_nr_corners
+     * (returned by Init()).
+     * The results of matching are in id_l and id_r.
+     * Interpretaqtion of results: if id_l[i] = m and id_r[i] = n,
+     * feature at (x_l[m],y_l[m]) matched to (x_r[n],y_r[n]).
+     * \param l_img     left image
+     * \param r_img     right image
+     * \param x_l       left x coordinates of features
+     * \param y_l       left y coordinates of features
+     * \param nr_l      number of features in left image
+     * \param x_r       right x coordinates of features
+     * \param y_r       right y coordinates of features
+     * \param nr_r      number of features in right image
+     * \param id_l      indices of left features that matched
+     * \param id_r      indices of right features that matched
+     * \param nr_matches    number of features actually matched
+     * \param H         image homography (prewarp) to be applied to right image features
+     * \param affine    prewarp the 11x11 patches by given affine transform. 0 means no warping,
+                        1 means nearest neighbor, 2 means bilinear warping.
+     */
+    virtual void Match(const unsigned char * const *l_img,const unsigned char * const *r_img,
+        const double *x_l,const double *y_l,int nr_l,const double *x_r,const double *y_r,int nr_r,
+        int *id_l,int *id_r,int *nr_matches,const double H[9]=0,int affine=0);
+
+    /*!
+     * Checks if Init() was called.
+     * \return 1 if Init() was called, 0 otherwise.
+     */
+    int IsAllocated();
+
+protected:
+    virtual void Clean();
+
+
+    int m_w,m_h,m_bw,m_bh,m_nr_h,m_nr_v,m_bd,m_target;
+    unsigned long m_kA,m_kB;
+    db_Bucket_u **m_bp_l;
+    db_Bucket_u **m_bp_r;
+    short *m_patch_space,*m_aligned_patch_space;
+
+    double m_max_disparity, m_max_disparity_v;
+    int m_rect_window;
+    bool m_use_smaller_matching_window;
+    int m_use_21;
+};
+
+
+
+#endif /*DB_FEATURE_MATCHING_H*/
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_framestitching.cpp b/jni_mosaic/feature_stab/db_vlvm/db_framestitching.cpp
new file mode 100644
index 0000000..b574f7a
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_framestitching.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_framestitching.cpp,v 1.2 2011/06/17 14:03:30 mbansal Exp $ */
+
+#include "db_utilities.h"
+#include "db_framestitching.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+inline void db_RotationFromMOuterProductSum(double R[9],double *score,double M[9])
+{
+    double N[16],q[4],lambda[4],lambda_max;
+    double y[4];
+    int nr_roots;
+
+    N[0]=   M[0]+M[4]+M[8];
+    N[5]=   M[0]-M[4]-M[8];
+    N[10]= -M[0]+M[4]-M[8];
+    N[15]= -M[0]-M[4]+M[8];
+    N[1] =N[4] =M[5]-M[7];
+    N[2] =N[8] =M[6]-M[2];
+    N[3] =N[12]=M[1]-M[3];
+    N[6] =N[9] =M[1]+M[3];
+    N[7] =N[13]=M[6]+M[2];
+    N[11]=N[14]=M[5]+M[7];
+
+    /*get the quaternion representing the rotation
+    by finding the eigenvector corresponding to the most
+    positive eigenvalue. Force eigenvalue solutions, since the matrix
+    is symmetric and solutions might otherwise be lost
+    when the data is planar*/
+    db_RealEigenvalues4x4(lambda,&nr_roots,N,1);
+    if(nr_roots)
+    {
+        lambda_max=lambda[0];
+        if(nr_roots>=2)
+        {
+            if(lambda[1]>lambda_max) lambda_max=lambda[1];
+            if(nr_roots>=3)
+            {
+                if(lambda[2]>lambda_max) lambda_max=lambda[2];
+                {
+                    if(nr_roots>=4) if(lambda[3]>lambda_max) lambda_max=lambda[3];
+                }
+            }
+        }
+    }
+    else lambda_max=1.0;
+    db_EigenVector4x4(q,lambda_max,N);
+
+    /*Compute the rotation matrix*/
+    db_QuaternionToRotation(R,q);
+
+    if(score)
+    {
+        /*Compute score=transpose(q)*N*q */
+        db_Multiply4x4_4x1(y,N,q);
+        *score=db_ScalarProduct4(q,y);
+    }
+}
+
+void db_StitchSimilarity3DRaw(double *scale,double R[9],double t[3],
+                            double **Xp,double **X,int nr_points,int orientation_preserving,
+                            int allow_scaling,int allow_rotation,int allow_translation)
+{
+    int i;
+    double c[3],cp[3],r[3],rp[3],M[9],s,sp,sc;
+    double Rr[9],score_p,score_r;
+    double *temp,*temp_p;
+
+    if(allow_translation)
+    {
+        db_PointCentroid3D(c,X,nr_points);
+        db_PointCentroid3D(cp,Xp,nr_points);
+    }
+    else
+    {
+        db_Zero3(c);
+        db_Zero3(cp);
+    }
+
+    db_Zero9(M);
+    s=sp=0;
+    for(i=0;i<nr_points;i++)
+    {
+        temp=   *X++;
+        temp_p= *Xp++;
+        r[0]=(*temp++)-c[0];
+        r[1]=(*temp++)-c[1];
+        r[2]=(*temp++)-c[2];
+        rp[0]=(*temp_p++)-cp[0];
+        rp[1]=(*temp_p++)-cp[1];
+        rp[2]=(*temp_p++)-cp[2];
+
+        M[0]+=r[0]*rp[0];
+        M[1]+=r[0]*rp[1];
+        M[2]+=r[0]*rp[2];
+        M[3]+=r[1]*rp[0];
+        M[4]+=r[1]*rp[1];
+        M[5]+=r[1]*rp[2];
+        M[6]+=r[2]*rp[0];
+        M[7]+=r[2]*rp[1];
+        M[8]+=r[2]*rp[2];
+
+        s+=db_sqr(r[0])+db_sqr(r[1])+db_sqr(r[2]);
+        sp+=db_sqr(rp[0])+db_sqr(rp[1])+db_sqr(rp[2]);
+    }
+
+    /*Compute scale*/
+    if(allow_scaling) sc=sqrt(db_SafeDivision(sp,s));
+    else sc=1.0;
+    *scale=sc;
+
+    /*Compute rotation*/
+    if(allow_rotation)
+    {
+        if(orientation_preserving)
+        {
+            db_RotationFromMOuterProductSum(R,0,M);
+        }
+        else
+        {
+            /*Try preserving*/
+            db_RotationFromMOuterProductSum(R,&score_p,M);
+            /*Try reversing*/
+            M[6]= -M[6];
+            M[7]= -M[7];
+            M[8]= -M[8];
+            db_RotationFromMOuterProductSum(Rr,&score_r,M);
+            if(score_r>score_p)
+            {
+                /*Reverse is better*/
+                R[0]=Rr[0]; R[1]=Rr[1]; R[2]= -Rr[2];
+                R[3]=Rr[3]; R[4]=Rr[4]; R[5]= -Rr[5];
+                R[6]=Rr[6]; R[7]=Rr[7]; R[8]= -Rr[8];
+            }
+        }
+    }
+    else db_Identity3x3(R);
+
+    /*Compute translation*/
+    if(allow_translation)
+    {
+        t[0]=cp[0]-sc*(R[0]*c[0]+R[1]*c[1]+R[2]*c[2]);
+        t[1]=cp[1]-sc*(R[3]*c[0]+R[4]*c[1]+R[5]*c[2]);
+        t[2]=cp[2]-sc*(R[6]*c[0]+R[7]*c[1]+R[8]*c[2]);
+    }
+    else db_Zero3(t);
+}
+
+
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_framestitching.h b/jni_mosaic/feature_stab/db_vlvm/db_framestitching.h
new file mode 100644
index 0000000..5fef5f3
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_framestitching.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_framestitching.h,v 1.2 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_FRAMESTITCHING_H
+#define DB_FRAMESTITCHING_H
+/*!
+ * \defgroup FrameStitching Frame Stitching (2D and 3D homography estimation)
+ */
+/*\{*/
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMFrameStitching (LM) Frame Stitching (2D and 3D homography estimation)
+ */
+/*\{*/
+
+/*!
+Find scale, rotation and translation of the similarity that
+takes the nr_points inhomogenous 3D points X to Xp
+(left to right according to Horn), i.e. for the homogenous equivalents
+Xp and X we would have
+\code
+    Xp~
+    [sR t]*X
+    [0  1]
+\endcode
+If orientation_preserving is true, R is restricted such that det(R)>0.
+allow_scaling, allow_rotation and allow_translation allow s,R and t
+to differ from 1,Identity and 0
+
+Full similarity takes the following on 550MHz:
+\code
+4.5 microseconds with       3 points
+4.7 microseconds with       4 points
+5.0 microseconds with       5 points
+5.2 microseconds with       6 points
+5.8 microseconds with      10 points
+20  microseconds with     100 points
+205 microseconds with    1000 points
+2.9 milliseconds with   10000 points
+50  milliseconds with  100000 points
+0.5 seconds      with 1000000 points
+\endcode
+Without orientation_preserving:
+\code
+4 points is minimal for (s,R,t) (R,t)
+3 points is minimal for (s,R) (R)
+2 points is minimal for (s,t)
+1 point is minimal for  (s) (t)
+\endcode
+With orientation_preserving:
+\code
+3 points is minimal for (s,R,t) (R,t)
+2 points is minimal for (s,R) (s,t) (R)
+1 point is minimal for  (s) (t)
+\endcode
+
+\param scale                    scale
+\param R                        rotation
+\param t                        translation
+\param Xp                       inhomogenouse 3D points in first coordinate system
+\param X                        inhomogenouse 3D points in second coordinate system
+\param nr_points                number of points
+\param orientation_preserving   if true, R is restricted such that det(R)>0.
+\param allow_scaling            estimate scale
+\param allow_rotation           estimate rotation
+\param allow_translation        estimate translation
+*/
+DB_API void db_StitchSimilarity3DRaw(double *scale,double R[9],double t[3],
+                            double **Xp,double **X,int nr_points,int orientation_preserving=1,
+                            int allow_scaling=1,int allow_rotation=1,int allow_translation=1);
+
+
+/*\}*/
+
+#endif /* DB_FRAMESTITCHING_H */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_image_homography.cpp b/jni_mosaic/feature_stab/db_vlvm/db_image_homography.cpp
new file mode 100644
index 0000000..aaad7f8
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_image_homography.cpp
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_image_homography.cpp,v 1.2 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities.h"
+#include "db_image_homography.h"
+#include "db_framestitching.h"
+#include "db_metrics.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+/*Compute the linear constraint on H obtained by requiring that the
+ratio between coordinate i_num and i_den of xp is equal to the ratio
+between coordinate i_num and i_den of Hx. i_zero should be set to
+the coordinate not equal to i_num or i_den. No normalization is used*/
+inline void db_SProjImagePointPointConstraint(double c[9],int i_num,int i_den,int i_zero,
+                           double xp[3],double x[3])
+{
+    db_MultiplyScalarCopy3(c+3*i_den,x,  xp[i_num]);
+    db_MultiplyScalarCopy3(c+3*i_num,x, -xp[i_den]);
+    db_Zero3(c+3*i_zero);
+}
+
+/*Compute two constraints on H generated by the correspondence (Xp,X),
+assuming that Xp ~= H*X. No normalization is used*/
+inline void db_SProjImagePointPointConstraints(double c1[9],double c2[9],double xp[3],double x[3])
+{
+    int ma_ind;
+
+    /*Find index of coordinate of Xp with largest absolute value*/
+    ma_ind=db_MaxAbsIndex3(xp);
+
+    /*Generate 2 constraints,
+    each constraint is generated by considering the ratio between a
+    coordinate and the largest absolute value coordinate*/
+    switch(ma_ind)
+    {
+    case 0:
+        db_SProjImagePointPointConstraint(c1,1,0,2,xp,x);
+        db_SProjImagePointPointConstraint(c2,2,0,1,xp,x);
+        break;
+    case 1:
+        db_SProjImagePointPointConstraint(c1,0,1,2,xp,x);
+        db_SProjImagePointPointConstraint(c2,2,1,0,xp,x);
+        break;
+    default:
+        db_SProjImagePointPointConstraint(c1,0,2,1,xp,x);
+        db_SProjImagePointPointConstraint(c2,1,2,0,xp,x);
+    }
+}
+
+inline void db_SAffineImagePointPointConstraints(double c1[7],double c2[7],double xp[3],double x[3])
+{
+    double ct1[9],ct2[9];
+
+    db_SProjImagePointPointConstraints(ct1,ct2,xp,x);
+    db_Copy6(c1,ct1); c1[6]=ct1[8];
+    db_Copy6(c2,ct2); c2[6]=ct2[8];
+}
+
+void db_StitchProjective2D_4Points(double H[9],
+                                      double x1[3],double x2[3],double x3[3],double x4[3],
+                                      double xp1[3],double xp2[3],double xp3[3],double xp4[3])
+{
+    double c[72];
+
+    /*Collect the constraints*/
+    db_SProjImagePointPointConstraints(c   ,c+9 ,xp1,x1);
+    db_SProjImagePointPointConstraints(c+18,c+27,xp2,x2);
+    db_SProjImagePointPointConstraints(c+36,c+45,xp3,x3);
+    db_SProjImagePointPointConstraints(c+54,c+63,xp4,x4);
+    /*Solve for the nullvector*/
+    db_NullVector8x9Destructive(H,c);
+}
+
+void db_StitchAffine2D_3Points(double H[9],
+                                      double x1[3],double x2[3],double x3[3],
+                                      double xp1[3],double xp2[3],double xp3[3])
+{
+    double c[42];
+
+    /*Collect the constraints*/
+    db_SAffineImagePointPointConstraints(c   ,c+7 ,xp1,x1);
+    db_SAffineImagePointPointConstraints(c+14,c+21,xp2,x2);
+    db_SAffineImagePointPointConstraints(c+28,c+35,xp3,x3);
+    /*Solve for the nullvector*/
+    db_NullVector6x7Destructive(H,c);
+    db_MultiplyScalar6(H,db_SafeReciprocal(H[6]));
+    H[6]=H[7]=0; H[8]=1.0;
+}
+
+/*Compute up to three solutions for the focal length given two point correspondences
+generated by a rotation with a common unknown focal length. No specific normalization
+of the input points is required. If signed_disambiguation is true, the points are
+required to be in front of the camera*/
+inline void db_CommonFocalLengthFromRotation_2Point(double fsol[3],int *nr_sols,double x1[3],double x2[3],double xp1[3],double xp2[3],int signed_disambiguation=1)
+{
+    double m,ax,ay,apx,apy,bx,by,bpx,bpy;
+    double p1[2],p2[2],p3[2],p4[2],p5[2],p6[2];
+    double p7[3],p8[4],p9[5],p10[3],p11[4];
+    double roots[3];
+    int nr_roots,i,j;
+
+    /*Solve for focal length using the equation
+    <a,b>^2*<ap,ap><bp,bp>=<ap,bp>^2*<a,a><b,b>
+    where a and ap are the homogenous vectors in the first image
+    after focal length scaling and b,bp are the vectors in the
+    second image*/
+
+    /*Normalize homogenous coordinates so that last coordinate is one*/
+    m=db_SafeReciprocal(x1[2]);
+    ax=x1[0]*m;
+    ay=x1[1]*m;
+    m=db_SafeReciprocal(xp1[2]);
+    apx=xp1[0]*m;
+    apy=xp1[1]*m;
+    m=db_SafeReciprocal(x2[2]);
+    bx=x2[0]*m;
+    by=x2[1]*m;
+    m=db_SafeReciprocal(xp2[2]);
+    bpx=xp2[0]*m;
+    bpy=xp2[1]*m;
+
+    /*Compute cubic in l=1/(f^2)
+    by dividing out the root l=0 from the equation
+    (l(ax*bx+ay*by)+1)^2*(l(apx^2+apy^2)+1)*(l(bpx^2+bpy^2)+1)=
+    (l(apx*bpx+apy*bpy)+1)^2*(l(ax^2+ay^2)+1)*(l(bx^2+by^2)+1)*/
+    p1[1]=ax*bx+ay*by;
+    p2[1]=db_sqr(apx)+db_sqr(apy);
+    p3[1]=db_sqr(bpx)+db_sqr(bpy);
+    p4[1]=apx*bpx+apy*bpy;
+    p5[1]=db_sqr(ax)+db_sqr(ay);
+    p6[1]=db_sqr(bx)+db_sqr(by);
+    p1[0]=p2[0]=p3[0]=p4[0]=p5[0]=p6[0]=1;
+
+    db_MultiplyPoly1_1(p7,p1,p1);
+    db_MultiplyPoly1_2(p8,p2,p7);
+    db_MultiplyPoly1_3(p9,p3,p8);
+
+    db_MultiplyPoly1_1(p10,p4,p4);
+    db_MultiplyPoly1_2(p11,p5,p10);
+    db_SubtractPolyProduct1_3(p9,p6,p11);
+    /*Cubic starts at p9[1]*/
+    db_SolveCubic(roots,&nr_roots,p9[4],p9[3],p9[2],p9[1]);
+
+    for(j=0,i=0;i<nr_roots;i++)
+    {
+        if(roots[i]>0)
+        {
+            if((!signed_disambiguation) || (db_PolyEval1(p1,roots[i])*db_PolyEval1(p4,roots[i])>0))
+            {
+                fsol[j++]=db_SafeSqrtReciprocal(roots[i]);
+            }
+        }
+    }
+    *nr_sols=j;
+}
+
+int db_StitchRotationCommonFocalLength_3Points(double H[9],double x1[3],double x2[3],double x3[3],double xp1[3],double xp2[3],double xp3[3],double *f,int signed_disambiguation)
+{
+    double fsol[3];
+    int nr_sols,i,best_sol,done;
+    double cost,best_cost;
+    double m,hyp[27],x1_temp[3],x2_temp[3],xp1_temp[3],xp2_temp[3];
+    double *hyp_point,ft;
+    double y[2];
+
+    db_CommonFocalLengthFromRotation_2Point(fsol,&nr_sols,x1,x2,xp1,xp2,signed_disambiguation);
+    if(nr_sols)
+    {
+        db_DeHomogenizeImagePoint(y,xp3);
+        done=0;
+        for(i=0;i<nr_sols;i++)
+        {
+            ft=fsol[i];
+            m=db_SafeReciprocal(ft);
+            x1_temp[0]=x1[0]*m;
+            x1_temp[1]=x1[1]*m;
+            x1_temp[2]=x1[2];
+            x2_temp[0]=x2[0]*m;
+            x2_temp[1]=x2[1]*m;
+            x2_temp[2]=x2[2];
+            xp1_temp[0]=xp1[0]*m;
+            xp1_temp[1]=xp1[1]*m;
+            xp1_temp[2]=xp1[2];
+            xp2_temp[0]=xp2[0]*m;
+            xp2_temp[1]=xp2[1]*m;
+            xp2_temp[2]=xp2[2];
+
+            hyp_point=hyp+9*i;
+            db_StitchCameraRotation_2Points(hyp_point,x1_temp,x2_temp,xp1_temp,xp2_temp);
+            hyp_point[2]*=ft;
+            hyp_point[5]*=ft;
+            hyp_point[6]*=m;
+            hyp_point[7]*=m;
+            cost=db_SquaredReprojectionErrorHomography(y,hyp_point,x3);
+
+            if(!done || cost<best_cost)
+            {
+                done=1;
+                best_cost=cost;
+                best_sol=i;
+            }
+        }
+
+        if(f) *f=fsol[best_sol];
+        db_Copy9(H,hyp+9*best_sol);
+        return(1);
+    }
+    else
+    {
+        db_Identity3x3(H);
+        if(f) *f=1.0;
+        return(0);
+    }
+}
+
+void db_StitchSimilarity2DRaw(double *scale,double R[4],double t[2],
+                            double **Xp,double **X,int nr_points,int orientation_preserving,
+                            int allow_scaling,int allow_rotation,int allow_translation)
+{
+    int i;
+    double c[2],cp[2],r[2],rp[2],M[4],s,sp,sc;
+    double *temp,*temp_p;
+    double Aacc,Bacc,Aacc2,Bacc2,divisor,divisor2,m,Am,Bm;
+
+    if(allow_translation)
+    {
+        db_PointCentroid2D(c,X,nr_points);
+        db_PointCentroid2D(cp,Xp,nr_points);
+    }
+    else
+    {
+        db_Zero2(c);
+        db_Zero2(cp);
+    }
+
+    db_Zero4(M);
+    s=sp=0;
+    for(i=0;i<nr_points;i++)
+    {
+        temp=   *X++;
+        temp_p= *Xp++;
+        r[0]=(*temp++)-c[0];
+        r[1]=(*temp++)-c[1];
+        rp[0]=(*temp_p++)-cp[0];
+        rp[1]=(*temp_p++)-cp[1];
+
+        M[0]+=r[0]*rp[0];
+        M[1]+=r[0]*rp[1];
+        M[2]+=r[1]*rp[0];
+        M[3]+=r[1]*rp[1];
+
+        s+=db_sqr(r[0])+db_sqr(r[1]);
+        sp+=db_sqr(rp[0])+db_sqr(rp[1]);
+    }
+
+    /*Compute scale*/
+    if(allow_scaling) sc=sqrt(db_SafeDivision(sp,s));
+    else sc=1.0;
+    *scale=sc;
+
+    /*Compute rotation*/
+    if(allow_rotation)
+    {
+        /*orientation preserving*/
+        Aacc=M[0]+M[3];
+        Bacc=M[2]-M[1];
+        /*orientation reversing*/
+        Aacc2=M[0]-M[3];
+        Bacc2=M[2]+M[1];
+        if(Aacc!=0.0 || Bacc!=0.0)
+        {
+            divisor=sqrt(Aacc*Aacc+Bacc*Bacc);
+            m=db_SafeReciprocal(divisor);
+            Am=Aacc*m;
+            Bm=Bacc*m;
+            R[0]=  Am;
+            R[1]=  Bm;
+            R[2]= -Bm;
+            R[3]=  Am;
+        }
+        else
+        {
+            db_Identity2x2(R);
+            divisor=0.0;
+        }
+        if(!orientation_preserving && (Aacc2!=0.0 || Bacc2!=0.0))
+        {
+            divisor2=sqrt(Aacc2*Aacc2+Bacc2*Bacc2);
+            if(divisor2>divisor)
+            {
+                m=db_SafeReciprocal(divisor2);
+                Am=Aacc2*m;
+                Bm=Bacc2*m;
+                R[0]=  Am;
+                R[1]=  Bm;
+                R[2]=  Bm;
+                R[3]= -Am;
+            }
+        }
+    }
+    else db_Identity2x2(R);
+
+    /*Compute translation*/
+    if(allow_translation)
+    {
+        t[0]=cp[0]-sc*(R[0]*c[0]+R[1]*c[1]);
+        t[1]=cp[1]-sc*(R[2]*c[0]+R[3]*c[1]);
+    }
+    else db_Zero2(t);
+}
+
+
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_image_homography.h b/jni_mosaic/feature_stab/db_vlvm/db_image_homography.h
new file mode 100644
index 0000000..165447d
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_image_homography.h
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_image_homography.h,v 1.2 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_IMAGE_HOMOGRAPHY
+#define DB_IMAGE_HOMOGRAPHY
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_framestitching.h"
+/*!
+ * \defgroup LMImageHomography (LM) Image Homography Estimation (feature based)
+ */
+/*\{*/
+/*!
+Solve for projective H such that xp~Hx. Prior normalization is not necessary,
+although desirable for numerical conditioning
+\param H    image projective (out)
+\param x1   image 1 point 1
+\param x2   image 1 point 2
+\param x3   image 1 point 3
+\param x4   image 1 point 4
+\param xp1  image 2 point 1
+\param xp2  image 2 point 2
+\param xp3  image 2 point 3
+\param xp4  image 2 point 4
+*/
+DB_API void db_StitchProjective2D_4Points(double H[9],
+                                      double x1[3],double x2[3],double x3[3],double x4[3],
+                                      double xp1[3],double xp2[3],double xp3[3],double xp4[3]);
+
+/*!
+Solve for affine H such that xp~Hx. Prior normalization is not necessary,
+although desirable for numerical conditioning
+\param H    image projective (out)
+\param x1   image 1 point 1
+\param x2   image 1 point 2
+\param x3   image 1 point 3
+\param xp1  image 2 point 1
+\param xp2  image 2 point 2
+\param xp3  image 2 point 3
+*/
+DB_API void db_StitchAffine2D_3Points(double H[9],
+                                      double x1[3],double x2[3],double x3[3],
+                                      double xp1[3],double xp2[3],double xp3[3]);
+
+/*!
+Solve for rotation R such that xp~Rx.
+Image points have to be of unit norm for the least squares to be meaningful.
+\param R    image rotation (out)
+\param x1   image 1 point 1
+\param x2   image 1 point 2
+\param xp1  image 2 point 1
+\param xp2  image 2 point 2
+*/
+inline void db_StitchCameraRotation_2Points(double R[9],
+                                            /*Image points have to be of unit norm
+                                            for the least squares to be meaningful*/
+                                            double x1[3],double x2[3],
+                                            double xp1[3],double xp2[3])
+{
+    double* x[2];
+    double* xp[2];
+    double scale,t[3];
+
+    x[0]=x1;
+    x[1]=x2;
+    xp[0]=xp1;
+    xp[1]=xp2;
+    db_StitchSimilarity3DRaw(&scale,R,t,xp,x,2,1,0,1,0);
+}
+
+/*!
+Solve for a homography H generated by a rotation R with a common unknown focal length f, i.e.
+H=diag(f,f,1)*R*diag(1/f,1/f,1) such that xp~Hx.
+If signed_disambiguation is true, the points are
+required to be in front of the camera. No specific normalization of the homogenous points
+is required, although it could be desirable to keep x1,x2,xp1 and xp2 of reasonable magnitude.
+If a solution is obtained the function returns 1, otherwise 0. If the focal length is desired
+a valid pointer should be passed in f
+*/
+DB_API int db_StitchRotationCommonFocalLength_3Points(double H[9],double x1[3],double x2[3],double x3[3],
+                                                      double xp1[3],double xp2[3],double xp3[3],double *f=0,int signed_disambiguation=1);
+
+/*!
+Find scale, rotation and translation of the similarity that
+takes the nr_points inhomogenous 2D points X to Xp,
+i.e. for the homogenous equivalents
+Xp and X we would have
+\code
+Xp~
+[sR t]*X
+[0  1]
+\endcode
+If orientation_preserving is true, R is restricted such that det(R)>0.
+allow_scaling, allow_rotation and allow_translation allow s,R and t
+to differ from 1,Identity and 0
+
+Full similarity takes the following on 550MHz:
+\code
+0.9 microseconds with       2 points
+1.0 microseconds with       3 points
+1.1 microseconds with       4 points
+1.3 microseconds with       5 points
+1.4 microseconds with       6 points
+1.7 microseconds with      10 points
+9   microseconds with     100 points
+130 microseconds with    1000 points
+1.3 milliseconds with   10000 points
+35  milliseconds with  100000 points
+350 milliseconds with 1000000 points
+\endcode
+
+Without orientation_preserving:
+\code
+3 points is minimal for (s,R,t) (R,t)
+2 points is minimal for (s,t) (s,R) (R)
+1 point is minimal for  (s) (t)
+\endcode
+
+With orientation_preserving:
+\code
+2 points is minimal for (s,R,t) (R,t) (s,t)
+1 point is minimal for (s,R) (R) (s) (t)
+\endcode
+\param scale        (out)
+\param R            2D rotation (out)
+\param t            2D translation (out)
+\param Xp           (nr_points x 2) pointer to array of image points
+\param X            (nr_points x 2 ) pointer to array of image points
+\param nr_points    number of points
+\param orientation_preserving
+\param allow_scaling    compute scale (if 0, scale=1)
+\param allow_rotation   compute rotation (if 0, R=[I])
+\param allow_translation compute translation (if 0 t = [0,0]')
+*/
+DB_API void db_StitchSimilarity2DRaw(double *scale,double R[4],double t[2],
+                            double **Xp,double **X,int nr_points,int orientation_preserving=1,
+                            int allow_scaling=1,int allow_rotation=1,int allow_translation=1);
+/*!
+See db_StitchRotationCommonFocalLength_3Points().
+\param H            Image similarity transformation (out)
+\param Xp           (nr_points x 2) pointer to array of image points
+\param X            (nr_points x 2) pointer to array of image points
+\param nr_points    number of points
+\param orientation_preserving
+\param allow_scaling    compute scale (if 0, scale=1)
+\param allow_rotation   compute rotation (if 0, R=[I])
+\param allow_translation compute translation (if 0 t = [0,0]')
+*/
+inline void db_StitchSimilarity2D(double H[9],double **Xp,double **X,int nr_points,int orientation_preserving=1,
+                                  int allow_scaling=1,int allow_rotation=1,int allow_translation=1)
+{
+    double s,R[4],t[2];
+
+    db_StitchSimilarity2DRaw(&s,R,t,Xp,X,nr_points,orientation_preserving,
+        allow_scaling,allow_rotation,allow_translation);
+
+    H[0]=s*R[0]; H[1]=s*R[1]; H[2]=t[0];
+    H[3]=s*R[2]; H[4]=s*R[3]; H[5]=t[1];
+    db_Zero2(H+6);
+    H[8]=1.0;
+}
+/*\}*/
+#endif /* DB_IMAGE_HOMOGRAPHY */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_metrics.h b/jni_mosaic/feature_stab/db_vlvm/db_metrics.h
new file mode 100644
index 0000000..6b95458
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_metrics.h
@@ -0,0 +1,408 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_metrics.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_METRICS
+#define DB_METRICS
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_utilities.h"
+/*!
+ * \defgroup LMMetrics (LM) Metrics
+ */
+/*\{*/
+
+
+
+
+/*!
+Compute function value fp and Jacobian J of robustifier given input value f*/
+inline void db_CauchyDerivative(double J[4],double fp[2],const double f[2],double one_over_scale2)
+{
+    double x2,y2,r,r2,r2s,one_over_r2,fu,r_fu,one_over_r_fu;
+    double one_plus_r2s,half_dfu_dx,half_dfu_dy,coeff,coeff2,coeff3;
+    int at_zero;
+
+    /*The robustifier takes the input (x,y) and makes a new
+    vector (xp,yp) where
+    xp=sqrt(log(1+(x^2+y^2)*one_over_scale2))*x/sqrt(x^2+y^2)
+    yp=sqrt(log(1+(x^2+y^2)*one_over_scale2))*y/sqrt(x^2+y^2)
+    The new vector has the property
+    xp^2+yp^2=log(1+(x^2+y^2)*one_over_scale2)
+    i.e. when it is square-summed it gives the robust
+    reprojection error
+    Define
+    r2=(x^2+y^2) and
+    r2s=r2*one_over_scale2
+    fu=log(1+r2s)/r2
+    then
+    xp=sqrt(fu)*x
+    yp=sqrt(fu)*y
+    and
+    d(r2)/dx=2x
+    d(r2)/dy=2y
+    and
+    dfu/dx=d(r2)/dx*(r2s/(1+r2s)-log(1+r2s))/(r2*r2)
+    dfu/dy=d(r2)/dy*(r2s/(1+r2s)-log(1+r2s))/(r2*r2)
+    and
+    d(xp)/dx=1/(2sqrt(fu))*(dfu/dx)*x+sqrt(fu)
+    d(xp)/dy=1/(2sqrt(fu))*(dfu/dy)*x
+    d(yp)/dx=1/(2sqrt(fu))*(dfu/dx)*y
+    d(yp)/dy=1/(2sqrt(fu))*(dfu/dy)*y+sqrt(fu)
+    */
+
+    x2=db_sqr(f[0]);
+    y2=db_sqr(f[1]);
+    r2=x2+y2;
+    r=sqrt(r2);
+
+    if(r2<=0.0) at_zero=1;
+    else
+    {
+        one_over_r2=1.0/r2;
+        r2s=r2*one_over_scale2;
+        one_plus_r2s=1.0+r2s;
+        fu=log(one_plus_r2s)*one_over_r2;
+        r_fu=sqrt(fu);
+        if(r_fu<=0.0) at_zero=1;
+        else
+        {
+            one_over_r_fu=1.0/r_fu;
+            fp[0]=r_fu*f[0];
+            fp[1]=r_fu*f[1];
+            /*r2s is always >= 0*/
+            coeff=(r2s/one_plus_r2s*one_over_r2-fu)*one_over_r2;
+            half_dfu_dx=f[0]*coeff;
+            half_dfu_dy=f[1]*coeff;
+            coeff2=one_over_r_fu*half_dfu_dx;
+            coeff3=one_over_r_fu*half_dfu_dy;
+
+            J[0]=coeff2*f[0]+r_fu;
+            J[1]=coeff3*f[0];
+            J[2]=coeff2*f[1];
+            J[3]=coeff3*f[1]+r_fu;
+            at_zero=0;
+        }
+    }
+    if(at_zero)
+    {
+        /*Close to zero the robustifying mapping
+        becomes identity*sqrt(one_over_scale2)*/
+        fp[0]=0.0;
+        fp[1]=0.0;
+        J[0]=sqrt(one_over_scale2);
+        J[1]=0.0;
+        J[2]=0.0;
+        J[3]=J[0];
+    }
+}
+
+inline double db_SquaredReprojectionErrorHomography(const double y[2],const double H[9],const double x[3])
+{
+    double x0,x1,x2,mult;
+    double sd;
+
+    x0=H[0]*x[0]+H[1]*x[1]+H[2]*x[2];
+    x1=H[3]*x[0]+H[4]*x[1]+H[5]*x[2];
+    x2=H[6]*x[0]+H[7]*x[1]+H[8]*x[2];
+    mult=1.0/((x2!=0.0)?x2:1.0);
+    sd=db_sqr((y[0]-x0*mult))+db_sqr((y[1]-x1*mult));
+
+    return(sd);
+}
+
+inline double db_SquaredInhomogenousHomographyError(const double y[2],const double H[9],const double x[2])
+{
+    double x0,x1,x2,mult;
+    double sd;
+
+    x0=H[0]*x[0]+H[1]*x[1]+H[2];
+    x1=H[3]*x[0]+H[4]*x[1]+H[5];
+    x2=H[6]*x[0]+H[7]*x[1]+H[8];
+    mult=1.0/((x2!=0.0)?x2:1.0);
+    sd=db_sqr((y[0]-x0*mult))+db_sqr((y[1]-x1*mult));
+
+    return(sd);
+}
+
+/*!
+Return a constant divided by likelihood of a Cauchy distributed
+reprojection error given the image point y, homography H, image point
+point x and the squared scale coefficient one_over_scale2=1.0/(scale*scale)
+where scale is the half width at half maximum (hWahM) of the
+Cauchy distribution*/
+inline double db_ExpCauchyInhomogenousHomographyError(const double y[2],const double H[9],const double x[2],
+                                                      double one_over_scale2)
+{
+    double sd;
+    sd=db_SquaredInhomogenousHomographyError(y,H,x);
+    return(1.0+sd*one_over_scale2);
+}
+
+/*!
+Compute residual vector f between image point y and homography Hx of
+image point x. Also compute Jacobian of f with respect
+to an update dx of H*/
+inline void db_DerivativeInhomHomographyError(double Jf_dx[18],double f[2],const double y[2],const double H[9],
+                                              const double x[2])
+{
+    double xh,yh,zh,mult,mult2,xh_mult2,yh_mult2;
+    /*The Jacobian of the inhomogenous coordinates with respect to
+    the homogenous is
+    [1/zh  0  -xh/(zh*zh)]
+    [ 0  1/zh -yh/(zh*zh)]
+    The Jacobian of the homogenous coordinates with respect to dH is
+    [x0 x1 1  0  0 0  0  0 0]
+    [ 0  0 0 x0 x1 1  0  0 0]
+    [ 0  0 0  0  0 0 x0 x1 1]
+    The output Jacobian is minus their product, i.e.
+    [-x0/zh -x1/zh -1/zh    0      0     0    x0*xh/(zh*zh) x1*xh/(zh*zh) xh/(zh*zh)]
+    [   0      0     0   -x0/zh -x1/zh -1/zh  x0*yh/(zh*zh) x1*yh/(zh*zh) yh/(zh*zh)]*/
+
+    /*Compute warped point, which is the same as
+    homogenous coordinates of reprojection*/
+    xh=H[0]*x[0]+H[1]*x[1]+H[2];
+    yh=H[3]*x[0]+H[4]*x[1]+H[5];
+    zh=H[6]*x[0]+H[7]*x[1]+H[8];
+    mult=1.0/((zh!=0.0)?zh:1.0);
+    /*Compute inhomogenous residual*/
+    f[0]=y[0]-xh*mult;
+    f[1]=y[1]-yh*mult;
+    /*Compute Jacobian*/
+    mult2=mult*mult;
+    xh_mult2=xh*mult2;
+    yh_mult2=yh*mult2;
+    Jf_dx[0]= -x[0]*mult;
+    Jf_dx[1]= -x[1]*mult;
+    Jf_dx[2]= -mult;
+    Jf_dx[3]=0;
+    Jf_dx[4]=0;
+    Jf_dx[5]=0;
+    Jf_dx[6]=x[0]*xh_mult2;
+    Jf_dx[7]=x[1]*xh_mult2;
+    Jf_dx[8]=xh_mult2;
+    Jf_dx[9]=0;
+    Jf_dx[10]=0;
+    Jf_dx[11]=0;
+    Jf_dx[12]=Jf_dx[0];
+    Jf_dx[13]=Jf_dx[1];
+    Jf_dx[14]=Jf_dx[2];
+    Jf_dx[15]=x[0]*yh_mult2;
+    Jf_dx[16]=x[1]*yh_mult2;
+    Jf_dx[17]=yh_mult2;
+}
+
+/*!
+Compute robust residual vector f between image point y and homography Hx of
+image point x. Also compute Jacobian of f with respect
+to an update dH of H*/
+inline void db_DerivativeCauchyInhomHomographyReprojection(double Jf_dx[18],double f[2],const double y[2],const double H[9],
+                                                           const double x[2],double one_over_scale2)
+{
+    double Jf_dx_loc[18],f_loc[2];
+    double J[4],J0,J1,J2,J3;
+
+    /*Compute reprojection Jacobian*/
+    db_DerivativeInhomHomographyError(Jf_dx_loc,f_loc,y,H,x);
+    /*Compute robustifier Jacobian*/
+    db_CauchyDerivative(J,f,f_loc,one_over_scale2);
+
+    /*Multiply the robustifier Jacobian with
+    the reprojection Jacobian*/
+    J0=J[0];J1=J[1];J2=J[2];J3=J[3];
+    Jf_dx[0]=J0*Jf_dx_loc[0];
+    Jf_dx[1]=J0*Jf_dx_loc[1];
+    Jf_dx[2]=J0*Jf_dx_loc[2];
+    Jf_dx[3]=                J1*Jf_dx_loc[12];
+    Jf_dx[4]=                J1*Jf_dx_loc[13];
+    Jf_dx[5]=                J1*Jf_dx_loc[14];
+    Jf_dx[6]=J0*Jf_dx_loc[6]+J1*Jf_dx_loc[15];
+    Jf_dx[7]=J0*Jf_dx_loc[7]+J1*Jf_dx_loc[16];
+    Jf_dx[8]=J0*Jf_dx_loc[8]+J1*Jf_dx_loc[17];
+    Jf_dx[9]= J2*Jf_dx_loc[0];
+    Jf_dx[10]=J2*Jf_dx_loc[1];
+    Jf_dx[11]=J2*Jf_dx_loc[2];
+    Jf_dx[12]=                J3*Jf_dx_loc[12];
+    Jf_dx[13]=                J3*Jf_dx_loc[13];
+    Jf_dx[14]=                J3*Jf_dx_loc[14];
+    Jf_dx[15]=J2*Jf_dx_loc[6]+J3*Jf_dx_loc[15];
+    Jf_dx[16]=J2*Jf_dx_loc[7]+J3*Jf_dx_loc[16];
+    Jf_dx[17]=J2*Jf_dx_loc[8]+J3*Jf_dx_loc[17];
+}
+/*!
+Compute residual vector f between image point y and rotation of
+image point x by R. Also compute Jacobian of f with respect
+to an update dx of R*/
+inline void db_DerivativeInhomRotationReprojection(double Jf_dx[6],double f[2],const double y[2],const double R[9],
+                                                   const double x[2])
+{
+    double xh,yh,zh,mult,mult2,xh_mult2,yh_mult2;
+    /*The Jacobian of the inhomogenous coordinates with respect to
+    the homogenous is
+    [1/zh  0  -xh/(zh*zh)]
+    [ 0  1/zh -yh/(zh*zh)]
+    The Jacobian at zero of the homogenous coordinates with respect to
+    [sin(phi) sin(ohm) sin(kap)] is
+    [-rx2   0   rx1 ]
+    [  0   rx2 -rx0 ]
+    [ rx0 -rx1   0  ]
+    The output Jacobian is minus their product, i.e.
+    [1+xh*xh/(zh*zh) -xh*yh/(zh*zh)   -yh/zh]
+    [xh*yh/(zh*zh)   -1-yh*yh/(zh*zh)  xh/zh]*/
+
+    /*Compute rotated point, which is the same as
+    homogenous coordinates of reprojection*/
+    xh=R[0]*x[0]+R[1]*x[1]+R[2];
+    yh=R[3]*x[0]+R[4]*x[1]+R[5];
+    zh=R[6]*x[0]+R[7]*x[1]+R[8];
+    mult=1.0/((zh!=0.0)?zh:1.0);
+    /*Compute inhomogenous residual*/
+    f[0]=y[0]-xh*mult;
+    f[1]=y[1]-yh*mult;
+    /*Compute Jacobian*/
+    mult2=mult*mult;
+    xh_mult2=xh*mult2;
+    yh_mult2=yh*mult2;
+    Jf_dx[0]= 1.0+xh*xh_mult2;
+    Jf_dx[1]= -yh*xh_mult2;
+    Jf_dx[2]= -yh*mult;
+    Jf_dx[3]= -Jf_dx[1];
+    Jf_dx[4]= -1-yh*yh_mult2;
+    Jf_dx[5]= xh*mult;
+}
+
+/*!
+Compute robust residual vector f between image point y and rotation of
+image point x by R. Also compute Jacobian of f with respect
+to an update dx of R*/
+inline void db_DerivativeCauchyInhomRotationReprojection(double Jf_dx[6],double f[2],const double y[2],const double R[9],
+                                                         const double x[2],double one_over_scale2)
+{
+    double Jf_dx_loc[6],f_loc[2];
+    double J[4],J0,J1,J2,J3;
+
+    /*Compute reprojection Jacobian*/
+    db_DerivativeInhomRotationReprojection(Jf_dx_loc,f_loc,y,R,x);
+    /*Compute robustifier Jacobian*/
+    db_CauchyDerivative(J,f,f_loc,one_over_scale2);
+
+    /*Multiply the robustifier Jacobian with
+    the reprojection Jacobian*/
+    J0=J[0];J1=J[1];J2=J[2];J3=J[3];
+    Jf_dx[0]=J0*Jf_dx_loc[0]+J1*Jf_dx_loc[3];
+    Jf_dx[1]=J0*Jf_dx_loc[1]+J1*Jf_dx_loc[4];
+    Jf_dx[2]=J0*Jf_dx_loc[2]+J1*Jf_dx_loc[5];
+    Jf_dx[3]=J2*Jf_dx_loc[0]+J3*Jf_dx_loc[3];
+    Jf_dx[4]=J2*Jf_dx_loc[1]+J3*Jf_dx_loc[4];
+    Jf_dx[5]=J2*Jf_dx_loc[2]+J3*Jf_dx_loc[5];
+}
+
+
+
+/*!
+// remove the outliers whose projection error is larger than pre-defined
+*/
+inline int db_RemoveOutliers_Homography(const double H[9], double *x_i,double *xp_i, double *wp,double *im, double *im_p, double *im_r, double *im_raw,double *im_raw_p,int point_count,double scale, double thresh=DB_OUTLIER_THRESHOLD)
+{
+    double temp_valueE, t2;
+    int c;
+    int k1=0;
+    int k2=0;
+    int k3=0;
+    int numinliers=0;
+    int ind1;
+    int ind2;
+    int ind3;
+    int isinlier;
+
+    // experimentally determined
+    t2=1.0/(thresh*thresh*thresh*thresh);
+
+    // count the inliers
+    for(c=0;c<point_count;c++)
+    {
+        ind1=c<<1;
+        ind2=c<<2;
+        ind3=3*c;
+
+        temp_valueE=db_SquaredInhomogenousHomographyError(im_p+ind3,H,im+ind3);
+
+        isinlier=((temp_valueE<=t2)?1:0);
+
+        // if it is inlier, then copy the 3d and 2d correspondences
+        if (isinlier)
+        {
+            numinliers++;
+
+            x_i[k1]=x_i[ind1];
+            x_i[k1+1]=x_i[ind1+1];
+
+            xp_i[k1]=xp_i[ind1];
+            xp_i[k1+1]=xp_i[ind1+1];
+
+            k1=k1+2;
+
+            // original normalized pixel coordinates
+            im[k3]=im[ind3];
+            im[k3+1]=im[ind3+1];
+            im[k3+2]=im[ind3+2];
+
+            im_r[k3]=im_r[ind3];
+            im_r[k3+1]=im_r[ind3+1];
+            im_r[k3+2]=im_r[ind3+2];
+
+            im_p[k3]=im_p[ind3];
+            im_p[k3+1]=im_p[ind3+1];
+            im_p[k3+2]=im_p[ind3+2];
+
+            // left and right raw pixel coordinates
+            im_raw[k3] = im_raw[ind3];
+            im_raw[k3+1] = im_raw[ind3+1];
+            im_raw[k3+2] = im_raw[ind3+2]; // the index
+
+            im_raw_p[k3] = im_raw_p[ind3];
+            im_raw_p[k3+1] = im_raw_p[ind3+1];
+            im_raw_p[k3+2] = im_raw_p[ind3+2]; // the index
+
+            k3=k3+3;
+
+            // 3D coordinates
+            wp[k2]=wp[ind2];
+            wp[k2+1]=wp[ind2+1];
+            wp[k2+2]=wp[ind2+2];
+            wp[k2+3]=wp[ind2+3];
+
+            k2=k2+4;
+
+        }
+    }
+
+    return numinliers;
+}
+
+
+
+
+
+/*\}*/
+
+#endif /* DB_METRICS */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.cpp b/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.cpp
new file mode 100644
index 0000000..82dec0c
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.cpp
@@ -0,0 +1,1082 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_rob_image_homography.cpp,v 1.2 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities.h"
+#include "db_rob_image_homography.h"
+#include "db_bundle.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_image_homography.h"
+
+#ifdef _VERBOSE_
+#include <iostream>
+using namespace std;
+#endif /*VERBOSE*/
+
+inline double db_RobImageHomography_Cost(double H[9],int point_count,double *x_i,double *xp_i,double one_over_scale2)
+{
+    int c;
+    double back,acc,*x_i_temp,*xp_i_temp;
+
+    for(back=0.0,c=0;c<point_count;)
+    {
+        /*Take log of product of ten reprojection
+        errors to reduce nr of expensive log operations*/
+        if(c+9<point_count)
+        {
+            x_i_temp=x_i+(c<<1);
+            xp_i_temp=xp_i+(c<<1);
+
+            acc=db_ExpCauchyInhomogenousHomographyError(xp_i_temp,H,x_i_temp,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+2,H,x_i_temp+2,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+4,H,x_i_temp+4,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+6,H,x_i_temp+6,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+8,H,x_i_temp+8,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+10,H,x_i_temp+10,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+12,H,x_i_temp+12,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+14,H,x_i_temp+14,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+16,H,x_i_temp+16,one_over_scale2);
+            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+18,H,x_i_temp+18,one_over_scale2);
+            c+=10;
+        }
+        else
+        {
+            for(acc=1.0;c<point_count;c++)
+            {
+                acc*=db_ExpCauchyInhomogenousHomographyError(xp_i+(c<<1),H,x_i+(c<<1),one_over_scale2);
+            }
+        }
+        back+=log(acc);
+    }
+    return(back);
+}
+
+inline double db_RobImageHomography_Statistics(double H[9],int point_count,double *x_i,double *xp_i,double one_over_scale2,db_Statistics *stat,double thresh=DB_OUTLIER_THRESHOLD)
+{
+    int c,i;
+    double t2,frac;
+
+    t2=thresh*thresh;
+    for(i=0,c=0;c<point_count;c++)
+    {
+        i+=(db_SquaredInhomogenousHomographyError(xp_i+(c<<1),H,x_i+(c<<1))*one_over_scale2<=t2)?1:0;
+    }
+    frac=((double)i)/((double)(db_maxi(point_count,1)));
+
+#ifdef _VERBOSE_
+    std::cout << "Inlier Percentage RobImageHomography: " << frac*100.0 << "% out of " << point_count << " constraints" << std::endl;
+#endif /*_VERBOSE_*/
+
+    if(stat)
+    {
+        stat->nr_points=point_count;
+        stat->one_over_scale2=one_over_scale2;
+        stat->nr_inliers=i;
+        stat->inlier_fraction=frac;
+
+        stat->cost=db_RobImageHomography_Cost(H,point_count,x_i,xp_i,one_over_scale2);
+        stat->model_dimension=0;
+        /*stat->nr_parameters=;*/
+
+        stat->lambda1=log(4.0);
+        stat->lambda2=log(4.0*((double)db_maxi(1,stat->nr_points)));
+        stat->lambda3=10.0;
+        stat->gric=stat->cost+stat->lambda1*stat->model_dimension*((double)stat->nr_points)+stat->lambda2*((double)stat->nr_parameters);
+        stat->inlier_evidence=((double)stat->nr_inliers)-stat->lambda3*((double)stat->nr_parameters);
+    }
+
+    return(frac);
+}
+
+/*Compute min_Jtf and upper right of JtJ. Return cost.*/
+inline double db_RobImageHomography_Jacobians(double JtJ[81],double min_Jtf[9],double H[9],int point_count,double *x_i,double *xp_i,double one_over_scale2)
+{
+    double back,Jf_dx[18],f[2],temp,temp2;
+    int i;
+
+    db_Zero(JtJ,81);
+    db_Zero(min_Jtf,9);
+    for(back=0.0,i=0;i<point_count;i++)
+    {
+        /*Compute reprojection error vector and its Jacobian
+        for this point*/
+        db_DerivativeCauchyInhomHomographyReprojection(Jf_dx,f,xp_i+(i<<1),H,x_i+(i<<1),one_over_scale2);
+        /*Perform
+        min_Jtf-=Jf_dx*f[0] and
+        min_Jtf-=(Jf_dx+9)*f[1] to accumulate -Jt%f*/
+        db_RowOperation9(min_Jtf,Jf_dx,f[0]);
+        db_RowOperation9(min_Jtf,Jf_dx+9,f[1]);
+        /*Accumulate upper right of JtJ with outer product*/
+        temp=Jf_dx[0]; temp2=Jf_dx[9];
+        JtJ[0]+=temp*Jf_dx[0]+temp2*Jf_dx[9];
+        JtJ[1]+=temp*Jf_dx[1]+temp2*Jf_dx[10];
+        JtJ[2]+=temp*Jf_dx[2]+temp2*Jf_dx[11];
+        JtJ[3]+=temp*Jf_dx[3]+temp2*Jf_dx[12];
+        JtJ[4]+=temp*Jf_dx[4]+temp2*Jf_dx[13];
+        JtJ[5]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[6]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[7]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[8]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[1]; temp2=Jf_dx[10];
+        JtJ[10]+=temp*Jf_dx[1]+temp2*Jf_dx[10];
+        JtJ[11]+=temp*Jf_dx[2]+temp2*Jf_dx[11];
+        JtJ[12]+=temp*Jf_dx[3]+temp2*Jf_dx[12];
+        JtJ[13]+=temp*Jf_dx[4]+temp2*Jf_dx[13];
+        JtJ[14]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[15]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[16]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[17]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[2]; temp2=Jf_dx[11];
+        JtJ[20]+=temp*Jf_dx[2]+temp2*Jf_dx[11];
+        JtJ[21]+=temp*Jf_dx[3]+temp2*Jf_dx[12];
+        JtJ[22]+=temp*Jf_dx[4]+temp2*Jf_dx[13];
+        JtJ[23]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[24]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[25]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[26]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[3]; temp2=Jf_dx[12];
+        JtJ[30]+=temp*Jf_dx[3]+temp2*Jf_dx[12];
+        JtJ[31]+=temp*Jf_dx[4]+temp2*Jf_dx[13];
+        JtJ[32]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[33]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[34]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[35]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[4]; temp2=Jf_dx[13];
+        JtJ[40]+=temp*Jf_dx[4]+temp2*Jf_dx[13];
+        JtJ[41]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[42]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[43]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[44]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[5]; temp2=Jf_dx[14];
+        JtJ[50]+=temp*Jf_dx[5]+temp2*Jf_dx[14];
+        JtJ[51]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[52]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[53]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[6]; temp2=Jf_dx[15];
+        JtJ[60]+=temp*Jf_dx[6]+temp2*Jf_dx[15];
+        JtJ[61]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[62]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[7]; temp2=Jf_dx[16];
+        JtJ[70]+=temp*Jf_dx[7]+temp2*Jf_dx[16];
+        JtJ[71]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+        temp=Jf_dx[8]; temp2=Jf_dx[17];
+        JtJ[80]+=temp*Jf_dx[8]+temp2*Jf_dx[17];
+
+        /*Add square-sum to cost*/
+        back+=db_sqr(f[0])+db_sqr(f[1]);
+    }
+
+    return(back);
+}
+
+/*Compute min_Jtf and upper right of JtJ. Return cost*/
+inline double db_RobCamRotation_Jacobians(double JtJ[9],double min_Jtf[3],double H[9],int point_count,double *x_i,double *xp_i,double one_over_scale2)
+{
+    double back,Jf_dx[6],f[2];
+    int i,j;
+
+    db_Zero(JtJ,9);
+    db_Zero(min_Jtf,3);
+    for(back=0.0,i=0;i<point_count;i++)
+    {
+        /*Compute reprojection error vector and its Jacobian
+        for this point*/
+        j=(i<<1);
+        db_DerivativeCauchyInhomRotationReprojection(Jf_dx,f,xp_i+j,H,x_i+j,one_over_scale2);
+        /*Perform
+        min_Jtf-=Jf_dx*f[0] and
+        min_Jtf-=(Jf_dx+3)*f[1] to accumulate -Jt%f*/
+        db_RowOperation3(min_Jtf,Jf_dx,f[0]);
+        db_RowOperation3(min_Jtf,Jf_dx+3,f[1]);
+        /*Accumulate upper right of JtJ with outer product*/
+        JtJ[0]+=Jf_dx[0]*Jf_dx[0]+Jf_dx[3]*Jf_dx[3];
+        JtJ[1]+=Jf_dx[0]*Jf_dx[1]+Jf_dx[3]*Jf_dx[4];
+        JtJ[2]+=Jf_dx[0]*Jf_dx[2]+Jf_dx[3]*Jf_dx[5];
+        JtJ[4]+=Jf_dx[1]*Jf_dx[1]+Jf_dx[4]*Jf_dx[4];
+        JtJ[5]+=Jf_dx[1]*Jf_dx[2]+Jf_dx[4]*Jf_dx[5];
+        JtJ[8]+=Jf_dx[2]*Jf_dx[2]+Jf_dx[5]*Jf_dx[5];
+
+        /*Add square-sum to cost*/
+        back+=db_sqr(f[0])+db_sqr(f[1]);
+    }
+
+    return(back);
+}
+
+void db_RobCamRotation_Polish(double H[9],int point_count,double *x_i,double *xp_i,double one_over_scale2,
+                               int max_iterations,double improvement_requirement)
+{
+    int i,update,stop;
+    double lambda,cost,current_cost;
+    double JtJ[9],min_Jtf[3],dx[3],H_p_dx[9];
+
+    lambda=0.001;
+    for(update=1,stop=0,i=0;(stop<2) && (i<max_iterations);i++)
+    {
+        /*if first time since improvement, compute Jacobian and residual*/
+        if(update)
+        {
+            current_cost=db_RobCamRotation_Jacobians(JtJ,min_Jtf,H,point_count,x_i,xp_i,one_over_scale2);
+            update=0;
+        }
+
+#ifdef _VERBOSE_
+        /*std::cout << "Cost:" << current_cost << " ";*/
+#endif /*_VERBOSE_*/
+
+        /*Come up with a hypothesis dx
+        based on the current lambda*/
+        db_Compute_dx_3x3(dx,JtJ,min_Jtf,lambda);
+
+        /*Compute Cost(x+dx)*/
+        db_UpdateRotation(H_p_dx,H,dx);
+        cost=db_RobImageHomography_Cost(H_p_dx,point_count,x_i,xp_i,one_over_scale2);
+
+        /*Is there an improvement?*/
+        if(cost<current_cost)
+        {
+            /*improvement*/
+            if(current_cost-cost<current_cost*improvement_requirement) stop++;
+            else stop=0;
+            lambda*=0.1;
+            /*Move to the hypothesised position x+dx*/
+            current_cost=cost;
+            db_Copy9(H,H_p_dx);
+            db_OrthonormalizeRotation(H);
+            update=1;
+
+#ifdef _VERBOSE_
+        std::cout << "Step" << i << "Imp,Lambda=" << lambda << "Cost:" << current_cost << std::endl;
+#endif /*_VERBOSE_*/
+        }
+        else
+        {
+            /*no improvement*/
+            lambda*=10.0;
+            stop=0;
+        }
+    }
+}
+
+inline void db_RobImageHomographyFetchJacobian(double **JtJ_ref,double *min_Jtf,double **JtJ_temp_ref,double *min_Jtf_temp,int n,int *fetch_vector)
+{
+    int i,j,t;
+    double *t1,*t2;
+
+    for(i=0;i<n;i++)
+    {
+        t=fetch_vector[i];
+        min_Jtf[i]=min_Jtf_temp[t];
+        t1=JtJ_ref[i];
+        t2=JtJ_temp_ref[t];
+        for(j=i;j<n;j++)
+        {
+            t1[j]=t2[fetch_vector[j]];
+        }
+    }
+}
+
+inline void db_RobImageHomographyMultiplyJacobian(double **JtJ_ref,double *min_Jtf,double **JtJ_temp_ref,double *min_Jtf_temp,double **JE_dx_ref,int n)
+{
+    double JtJ_JE[72],*JtJ_JE_ref[9];
+
+    db_SetupMatrixRefs(JtJ_JE_ref,9,8,JtJ_JE);
+
+    db_SymmetricExtendUpperToLower(JtJ_temp_ref,9,9);
+    db_MultiplyMatricesAB(JtJ_JE_ref,JtJ_temp_ref,JE_dx_ref,9,9,n);
+    db_UpperMultiplyMatricesAtB(JtJ_ref,JE_dx_ref,JtJ_JE_ref,n,9,n);
+    db_MultiplyMatrixVectorAtb(min_Jtf,JE_dx_ref,min_Jtf_temp,n,9);
+}
+
+inline void db_RobImageHomographyJH_Js(double **JE_dx_ref,int j,double H[9])
+{
+    /*Update of upper 2x2 is multiplication by
+    [s 0][ cos(theta) sin(theta)]
+    [0 s][-sin(theta) cos(theta)]*/
+    JE_dx_ref[0][j]=H[0];
+    JE_dx_ref[1][j]=H[1];
+    JE_dx_ref[2][j]=0;
+    JE_dx_ref[3][j]=H[2];
+    JE_dx_ref[4][j]=H[3];
+    JE_dx_ref[5][j]=0;
+    JE_dx_ref[6][j]=0;
+    JE_dx_ref[7][j]=0;
+    JE_dx_ref[8][j]=0;
+}
+
+inline void db_RobImageHomographyJH_JR(double **JE_dx_ref,int j,double H[9])
+{
+    /*Update of upper 2x2 is multiplication by
+    [s 0][ cos(theta) sin(theta)]
+    [0 s][-sin(theta) cos(theta)]*/
+    JE_dx_ref[0][j]=  H[3];
+    JE_dx_ref[1][j]=  H[4];
+    JE_dx_ref[2][j]=0;
+    JE_dx_ref[3][j]= -H[0];
+    JE_dx_ref[4][j]= -H[1];
+    JE_dx_ref[5][j]=0;
+    JE_dx_ref[6][j]=0;
+    JE_dx_ref[7][j]=0;
+    JE_dx_ref[8][j]=0;
+}
+
+inline void db_RobImageHomographyJH_Jt(double **JE_dx_ref,int j,int k,double H[9])
+{
+    JE_dx_ref[0][j]=0;
+    JE_dx_ref[1][j]=0;
+    JE_dx_ref[2][j]=1.0;
+    JE_dx_ref[3][j]=0;
+    JE_dx_ref[4][j]=0;
+    JE_dx_ref[5][j]=0;
+    JE_dx_ref[6][j]=0;
+    JE_dx_ref[7][j]=0;
+    JE_dx_ref[8][j]=0;
+
+    JE_dx_ref[0][k]=0;
+    JE_dx_ref[1][k]=0;
+    JE_dx_ref[2][k]=0;
+    JE_dx_ref[3][k]=0;
+    JE_dx_ref[4][k]=0;
+    JE_dx_ref[5][k]=1.0;
+    JE_dx_ref[6][k]=0;
+    JE_dx_ref[7][k]=0;
+    JE_dx_ref[8][k]=0;
+}
+
+inline void db_RobImageHomographyJH_dRotFocal(double **JE_dx_ref,int j,int k,int l,int m,double H[9])
+{
+    double f,fi,fi2;
+    double R[9],J[9];
+
+    /*Updated matrix is diag(f+df,f+df)*dR*R*diag(1/(f+df),1/(f+df),1)*/
+    f=db_FocalAndRotFromCamRotFocalHomography(R,H);
+    fi=db_SafeReciprocal(f);
+    fi2=db_sqr(fi);
+    db_JacobianOfRotatedPointStride(J,R,3);
+    JE_dx_ref[0][j]=   J[0];
+    JE_dx_ref[1][j]=   J[1];
+    JE_dx_ref[2][j]=f* J[2];
+    JE_dx_ref[3][j]=   J[3];
+    JE_dx_ref[4][j]=   J[4];
+    JE_dx_ref[5][j]=f* J[5];
+    JE_dx_ref[6][j]=fi*J[6];
+    JE_dx_ref[7][j]=fi*J[7];
+    JE_dx_ref[8][j]=   J[8];
+    db_JacobianOfRotatedPointStride(J,R+1,3);
+    JE_dx_ref[0][k]=   J[0];
+    JE_dx_ref[1][k]=   J[1];
+    JE_dx_ref[2][k]=f* J[2];
+    JE_dx_ref[3][k]=   J[3];
+    JE_dx_ref[4][k]=   J[4];
+    JE_dx_ref[5][k]=f* J[5];
+    JE_dx_ref[6][k]=fi*J[6];
+    JE_dx_ref[7][k]=fi*J[7];
+    JE_dx_ref[8][k]=   J[8];
+    db_JacobianOfRotatedPointStride(J,R+2,3);
+    JE_dx_ref[0][l]=   J[0];
+    JE_dx_ref[1][l]=   J[1];
+    JE_dx_ref[2][l]=f* J[2];
+    JE_dx_ref[3][l]=   J[3];
+    JE_dx_ref[4][l]=   J[4];
+    JE_dx_ref[5][l]=f* J[5];
+    JE_dx_ref[6][l]=fi*J[6];
+    JE_dx_ref[7][l]=fi*J[7];
+    JE_dx_ref[8][l]=   J[8];
+
+    JE_dx_ref[0][m]=0;
+    JE_dx_ref[1][m]=0;
+    JE_dx_ref[2][m]=H[2];
+    JE_dx_ref[3][m]=0;
+    JE_dx_ref[4][m]=0;
+    JE_dx_ref[5][m]=H[5];
+    JE_dx_ref[6][m]= -fi2*H[6];
+    JE_dx_ref[7][m]= -fi2*H[7];
+    JE_dx_ref[8][m]=0;
+}
+
+inline double db_RobImageHomography_Jacobians_Generic(double *JtJ_ref[8],double min_Jtf[8],int *num_param,int *frozen_coord,double H[9],int point_count,double *x_i,double *xp_i,int homography_type,double one_over_scale2)
+{
+    double back;
+    int i,j,fetch_vector[8],n;
+    double JtJ_temp[81],min_Jtf_temp[9],JE_dx[72];
+    double *JE_dx_ref[9],*JtJ_temp_ref[9];
+
+    /*Compute cost and JtJ,min_Jtf with respect to H*/
+    back=db_RobImageHomography_Jacobians(JtJ_temp,min_Jtf_temp,H,point_count,x_i,xp_i,one_over_scale2);
+
+    /*Compute JtJ,min_Jtf with respect to the right parameters
+    The formulas are
+    JtJ=transpose(JE_dx)*JtJ*JE_dx and
+    min_Jtf=transpose(JE_dx)*min_Jtf,
+    where the 9xN matrix JE_dx is the Jacobian of H with respect
+    to the update*/
+    db_SetupMatrixRefs(JtJ_temp_ref,9,9,JtJ_temp);
+    db_SetupMatrixRefs(JE_dx_ref,9,8,JE_dx);
+    switch(homography_type)
+    {
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+            n=4;
+            db_RobImageHomographyJH_Js(JE_dx_ref,0,H);
+            db_RobImageHomographyJH_JR(JE_dx_ref,1,H);
+            db_RobImageHomographyJH_Jt(JE_dx_ref,2,3,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+        case DB_HOMOGRAPHY_TYPE_ROTATION:
+        case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+            n=1;
+            db_RobImageHomographyJH_JR(JE_dx_ref,0,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+        case DB_HOMOGRAPHY_TYPE_SCALING:
+            n=1;
+            db_RobImageHomographyJH_Js(JE_dx_ref,0,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+        case DB_HOMOGRAPHY_TYPE_S_T:
+            n=3;
+            db_RobImageHomographyJH_Js(JE_dx_ref,0,H);
+            db_RobImageHomographyJH_Jt(JE_dx_ref,1,2,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_T:
+            n=3;
+            db_RobImageHomographyJH_JR(JE_dx_ref,0,H);
+            db_RobImageHomographyJH_Jt(JE_dx_ref,1,2,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_S:
+            n=2;
+            db_RobImageHomographyJH_Js(JE_dx_ref,0,H);
+            db_RobImageHomographyJH_JR(JE_dx_ref,1,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+
+        case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+            n=2;
+            fetch_vector[0]=2;
+            fetch_vector[1]=5;
+            db_RobImageHomographyFetchJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,n,fetch_vector);
+            break;
+        case DB_HOMOGRAPHY_TYPE_AFFINE:
+            n=6;
+            fetch_vector[0]=0;
+            fetch_vector[1]=1;
+            fetch_vector[2]=2;
+            fetch_vector[3]=3;
+            fetch_vector[4]=4;
+            fetch_vector[5]=5;
+            db_RobImageHomographyFetchJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,n,fetch_vector);
+            break;
+        case DB_HOMOGRAPHY_TYPE_PROJECTIVE:
+            n=8;
+            *frozen_coord=db_MaxAbsIndex9(H);
+            for(j=0,i=0;i<9;i++) if(i!=(*frozen_coord))
+            {
+                fetch_vector[j]=i;
+                j++;
+            }
+            db_RobImageHomographyFetchJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,n,fetch_vector);
+            break;
+        case DB_HOMOGRAPHY_TYPE_CAMROTATION_F:
+        case DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD:
+            n=4;
+            db_RobImageHomographyJH_dRotFocal(JE_dx_ref,0,1,2,3,H);
+            db_RobImageHomographyMultiplyJacobian(JtJ_ref,min_Jtf,JtJ_temp_ref,min_Jtf_temp,JE_dx_ref,n);
+            break;
+    }
+    *num_param=n;
+
+    return(back);
+}
+
+inline void db_ImageHomographyUpdateGeneric(double H_p_dx[9],double H[9],double *dx,int homography_type,int frozen_coord)
+{
+    switch(homography_type)
+    {
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+            db_Copy9(H_p_dx,H);
+            db_MultiplyScaleOntoImageHomography(H,1.0+dx[0]);
+            db_MultiplyRotationOntoImageHomography(H,dx[1]);
+            H_p_dx[2]+=dx[2];
+            H_p_dx[5]+=dx[3];
+            break;
+        case DB_HOMOGRAPHY_TYPE_ROTATION:
+        case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+            db_MultiplyRotationOntoImageHomography(H,dx[0]);
+            break;
+        case DB_HOMOGRAPHY_TYPE_SCALING:
+            db_MultiplyScaleOntoImageHomography(H,1.0+dx[0]);
+            break;
+        case DB_HOMOGRAPHY_TYPE_S_T:
+            db_Copy9(H_p_dx,H);
+            db_MultiplyScaleOntoImageHomography(H,1.0+dx[0]);
+            H_p_dx[2]+=dx[1];
+            H_p_dx[5]+=dx[2];
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_T:
+            db_Copy9(H_p_dx,H);
+            db_MultiplyRotationOntoImageHomography(H,dx[0]);
+            H_p_dx[2]+=dx[1];
+            H_p_dx[5]+=dx[2];
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_S:
+            db_Copy9(H_p_dx,H);
+            db_MultiplyScaleOntoImageHomography(H,1.0+dx[0]);
+            db_MultiplyRotationOntoImageHomography(H,dx[1]);
+            break;
+        case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+            db_Copy9(H_p_dx,H);
+            H_p_dx[2]+=dx[0];
+            H_p_dx[5]+=dx[1];
+            break;
+        case DB_HOMOGRAPHY_TYPE_AFFINE:
+            db_UpdateImageHomographyAffine(H_p_dx,H,dx);
+            break;
+        case DB_HOMOGRAPHY_TYPE_PROJECTIVE:
+            db_UpdateImageHomographyProjective(H_p_dx,H,dx,frozen_coord);
+            break;
+        case DB_HOMOGRAPHY_TYPE_CAMROTATION_F:
+        case DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD:
+            db_UpdateRotFocalHomography(H_p_dx,H,dx);
+            break;
+    }
+}
+
+void db_RobCamRotation_Polish_Generic(double H[9],int point_count,int homography_type,double *x_i,double *xp_i,double one_over_scale2,
+                               int max_iterations,double improvement_requirement)
+{
+    int i,update,stop,n;
+    int frozen_coord = 0;
+    double lambda,cost,current_cost;
+    double JtJ[72],min_Jtf[9],dx[8],H_p_dx[9];
+    double *JtJ_ref[9],d[8];
+
+    lambda=0.001;
+    for(update=1,stop=0,i=0;(stop<2) && (i<max_iterations);i++)
+    {
+        /*if first time since improvement, compute Jacobian and residual*/
+        if(update)
+        {
+            db_SetupMatrixRefs(JtJ_ref,9,8,JtJ);
+            current_cost=db_RobImageHomography_Jacobians_Generic(JtJ_ref,min_Jtf,&n,&frozen_coord,H,point_count,x_i,xp_i,homography_type,one_over_scale2);
+            update=0;
+        }
+
+#ifdef _VERBOSE_
+        /*std::cout << "Cost:" << current_cost << " ";*/
+#endif /*_VERBOSE_*/
+
+        /*Come up with a hypothesis dx
+        based on the current lambda*/
+        db_Compute_dx(dx,JtJ_ref,min_Jtf,lambda,d,n);
+
+        /*Compute Cost(x+dx)*/
+        db_ImageHomographyUpdateGeneric(H_p_dx,H,dx,homography_type,frozen_coord);
+        cost=db_RobImageHomography_Cost(H_p_dx,point_count,x_i,xp_i,one_over_scale2);
+
+        /*Is there an improvement?*/
+        if(cost<current_cost)
+        {
+            /*improvement*/
+            if(current_cost-cost<current_cost*improvement_requirement) stop++;
+            else stop=0;
+            lambda*=0.1;
+            /*Move to the hypothesised position x+dx*/
+            current_cost=cost;
+            db_Copy9(H,H_p_dx);
+            update=1;
+
+#ifdef _VERBOSE_
+        std::cout << "Step" << i << "Imp,Lambda=" << lambda << "Cost:" << current_cost << std::endl;
+#endif /*_VERBOSE_*/
+        }
+        else
+        {
+            /*no improvement*/
+            lambda*=10.0;
+            stop=0;
+        }
+    }
+}
+void db_RobImageHomography(
+                              /*Best homography*/
+                              double H[9],
+                              /*2DPoint to 2DPoint constraints
+                              Points are assumed to be given in
+                              homogenous coordinates*/
+                              double *im, double *im_p,
+                              /*Nr of points in total*/
+                              int nr_points,
+                              /*Calibration matrices
+                              used to normalize the points*/
+                              double K[9],
+                              double Kp[9],
+                              /*Pre-allocated space temp_d
+                              should point to at least
+                              12*nr_samples+10*nr_points
+                              allocated positions*/
+                              double *temp_d,
+                              /*Pre-allocated space temp_i
+                              should point to at least
+                              max(nr_samples,nr_points)
+                              allocated positions*/
+                              int *temp_i,
+                              int homography_type,
+                              db_Statistics *stat,
+                              int max_iterations,
+                              int max_points,
+                              double scale,
+                              int nr_samples,
+                              int chunk_size,
+                              /////////////////////////////////////////////
+                              // regular use: set outlierremoveflagE =0;
+                              // flag for the outlier removal
+                              int outlierremoveflagE,
+                              // if flag is 1, then the following variables
+                              // need the input
+                              //////////////////////////////////////
+                              // 3D coordinates
+                              double *wp,
+                              // its corresponding stereo pair's points
+                              double *im_r,
+                              // raw image coordinates
+                              double *im_raw, double *im_raw_p,
+                              // final matches
+                              int *finalNumE)
+{
+    /*Random seed*/
+    int r_seed;
+
+    int point_count_new;
+    /*Counters*/
+    int i,j,c,point_count,hyp_count;
+    int last_hyp,new_last_hyp,last_corr;
+    int pos,point_pos,last_point;
+    /*Accumulator*/
+    double acc;
+    /*Hypothesis pointer*/
+    double *hyp_point;
+    /*Random sample*/
+    int s[4];
+    /*Pivot for hypothesis pruning*/
+    double pivot;
+    /*Best hypothesis position*/
+    int best_pos;
+    /*Best score*/
+    double lowest_cost;
+    /*One over the squared scale of
+    Cauchy distribution*/
+    double one_over_scale2;
+    /*temporary pointers*/
+    double *x_i_temp,*xp_i_temp;
+    /*Temporary space for inverse calibration matrices*/
+    double K_inv[9];
+    double Kp_inv[9];
+    /*Temporary space for homography*/
+    double H_temp[9],H_temp2[9];
+    /*Pointers to homogenous coordinates*/
+    double *x_h_point,*xp_h_point;
+    /*Array of pointers to inhomogenous coordinates*/
+    double *X[3],*Xp[3];
+    /*Similarity parameters*/
+    int orientation_preserving,allow_scaling,allow_rotation,allow_translation,sample_size;
+
+    /*Homogenous coordinates of image points in first image*/
+    double *x_h;
+    /*Homogenous coordinates of image points in second image*/
+    double *xp_h;
+    /*Inhomogenous coordinates of image points in first image*/
+    double *x_i;
+    /*Inhomogenous coordinates of image points in second image*/
+    double *xp_i;
+    /*Homography hypotheses*/
+    double *hyp_H_array;
+    /*Cost array*/
+    double *hyp_cost_array;
+    /*Permutation of the hypotheses*/
+    int *hyp_perm;
+    /*Sample of the points*/
+    int *point_perm;
+    /*Temporary space for quick-select
+    2*nr_samples*/
+    double *temp_select;
+
+    /*Get inverse calibration matrices*/
+    db_InvertCalibrationMatrix(K_inv,K);
+    db_InvertCalibrationMatrix(Kp_inv,Kp);
+    /*Compute scale coefficient*/
+    one_over_scale2=1.0/(scale*scale);
+    /*Initialize random seed*/
+    r_seed=12345;
+    /*Set pointers to pre-allocated space*/
+    hyp_cost_array=temp_d;
+    hyp_H_array=temp_d+nr_samples;
+    temp_select=temp_d+10*nr_samples;
+    x_h=temp_d+12*nr_samples;
+    xp_h=temp_d+12*nr_samples+3*nr_points;
+    x_i=temp_d+12*nr_samples+6*nr_points;
+    xp_i=temp_d+12*nr_samples+8*nr_points;
+    hyp_perm=temp_i;
+    point_perm=temp_i;
+
+    /*Prepare a randomly permuted subset of size
+    point_count from the input points*/
+
+    point_count=db_mini(nr_points,(int)(chunk_size*log((double)nr_samples)/DB_LN2));
+
+    point_count_new = point_count;
+
+    for(i=0;i<nr_points;i++) point_perm[i]=i;
+
+    for(last_point=nr_points-1,i=0;i<point_count;i++,last_point--)
+    {
+        pos=db_RandomInt(r_seed,last_point);
+        point_pos=point_perm[pos];
+        point_perm[pos]=point_perm[last_point];
+
+        /*Normalize image points with calibration
+        matrices and move them to x_h and xp_h*/
+        c=3*point_pos;
+        j=3*i;
+        x_h_point=x_h+j;
+        xp_h_point=xp_h+j;
+        db_Multiply3x3_3x1(x_h_point,K_inv,im+c);
+        db_Multiply3x3_3x1(xp_h_point,Kp_inv,im_p+c);
+
+        db_HomogenousNormalize3(x_h_point);
+        db_HomogenousNormalize3(xp_h_point);
+
+        /*Dehomogenize image points and move them
+        to x_i and xp_i*/
+        c=(i<<1);
+        db_DeHomogenizeImagePoint(x_i+c,x_h_point); // 2-dimension
+        db_DeHomogenizeImagePoint(xp_i+c,xp_h_point); //2-dimension
+    }
+
+
+    /*Generate Hypotheses*/
+    hyp_count=0;
+    switch(homography_type)
+    {
+    case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+    case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+    case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+    case DB_HOMOGRAPHY_TYPE_ROTATION:
+    case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+    case DB_HOMOGRAPHY_TYPE_SCALING:
+    case DB_HOMOGRAPHY_TYPE_S_T:
+    case DB_HOMOGRAPHY_TYPE_R_T:
+    case DB_HOMOGRAPHY_TYPE_R_S:
+
+        switch(homography_type)
+        {
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+            orientation_preserving=1;
+            allow_scaling=1;
+            allow_rotation=1;
+            allow_translation=1;
+            sample_size=2;
+            break;
+        case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+            orientation_preserving=0;
+            allow_scaling=1;
+            allow_rotation=1;
+            allow_translation=1;
+            sample_size=3;
+            break;
+        case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+            orientation_preserving=1;
+            allow_scaling=0;
+            allow_rotation=0;
+            allow_translation=1;
+            sample_size=1;
+            break;
+        case DB_HOMOGRAPHY_TYPE_ROTATION:
+            orientation_preserving=1;
+            allow_scaling=0;
+            allow_rotation=1;
+            allow_translation=0;
+            sample_size=1;
+            break;
+        case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+            orientation_preserving=0;
+            allow_scaling=0;
+            allow_rotation=1;
+            allow_translation=0;
+            sample_size=2;
+            break;
+        case DB_HOMOGRAPHY_TYPE_SCALING:
+            orientation_preserving=1;
+            allow_scaling=1;
+            allow_rotation=0;
+            allow_translation=0;
+            sample_size=1;
+            break;
+        case DB_HOMOGRAPHY_TYPE_S_T:
+            orientation_preserving=1;
+            allow_scaling=1;
+            allow_rotation=0;
+            allow_translation=1;
+            sample_size=2;
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_T:
+            orientation_preserving=1;
+            allow_scaling=0;
+            allow_rotation=1;
+            allow_translation=1;
+            sample_size=2;
+            break;
+        case DB_HOMOGRAPHY_TYPE_R_S:
+            orientation_preserving=1;
+            allow_scaling=1;
+            allow_rotation=0;
+            allow_translation=0;
+            sample_size=1;
+            break;
+        }
+
+        if(point_count>=sample_size) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,3,point_count,r_seed);
+            X[0]= &x_i[s[0]<<1];
+            X[1]= &x_i[s[1]<<1];
+            X[2]= &x_i[s[2]<<1];
+            Xp[0]= &xp_i[s[0]<<1];
+            Xp[1]= &xp_i[s[1]<<1];
+            Xp[2]= &xp_i[s[2]<<1];
+            db_StitchSimilarity2D(&hyp_H_array[9*hyp_count],Xp,X,sample_size,orientation_preserving,
+                                  allow_scaling,allow_rotation,allow_translation);
+            hyp_count++;
+        }
+        break;
+
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION:
+        if(point_count>=2) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,2,point_count,r_seed);
+            db_StitchCameraRotation_2Points(&hyp_H_array[9*hyp_count],
+                                      &x_h[3*s[0]],&x_h[3*s[1]],
+                                      &xp_h[3*s[0]],&xp_h[3*s[1]]);
+            hyp_count++;
+        }
+        break;
+
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION_F:
+        if(point_count>=3) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,3,point_count,r_seed);
+            hyp_count+=db_StitchRotationCommonFocalLength_3Points(&hyp_H_array[9*hyp_count],
+                                      &x_h[3*s[0]],&x_h[3*s[1]],&x_h[3*s[2]],
+                                      &xp_h[3*s[0]],&xp_h[3*s[1]],&xp_h[3*s[2]]);
+        }
+        break;
+
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD:
+        if(point_count>=3) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,3,point_count,r_seed);
+            hyp_count+=db_StitchRotationCommonFocalLength_3Points(&hyp_H_array[9*hyp_count],
+                                      &x_h[3*s[0]],&x_h[3*s[1]],&x_h[3*s[2]],
+                                      &xp_h[3*s[0]],&xp_h[3*s[1]],&xp_h[3*s[2]],NULL,0);
+        }
+        break;
+
+    case DB_HOMOGRAPHY_TYPE_AFFINE:
+        if(point_count>=3) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,3,point_count,r_seed);
+            db_StitchAffine2D_3Points(&hyp_H_array[9*hyp_count],
+                                      &x_h[3*s[0]],&x_h[3*s[1]],&x_h[3*s[2]],
+                                      &xp_h[3*s[0]],&xp_h[3*s[1]],&xp_h[3*s[2]]);
+            hyp_count++;
+        }
+        break;
+
+    case DB_HOMOGRAPHY_TYPE_PROJECTIVE:
+    default:
+        if(point_count>=4) for(i=0;i<nr_samples;i++)
+        {
+            db_RandomSample(s,4,point_count,r_seed);
+            db_StitchProjective2D_4Points(&hyp_H_array[9*hyp_count],
+                                      &x_h[3*s[0]],&x_h[3*s[1]],&x_h[3*s[2]],&x_h[3*s[3]],
+                                      &xp_h[3*s[0]],&xp_h[3*s[1]],&xp_h[3*s[2]],&xp_h[3*s[3]]);
+            hyp_count++;
+        }
+    }
+
+    if(hyp_count)
+    {
+        /*Count cost in chunks and decimate hypotheses
+        until only one remains or the correspondences are
+        exhausted*/
+        for(i=0;i<hyp_count;i++)
+        {
+            hyp_perm[i]=i;
+            hyp_cost_array[i]=0.0;
+        }
+        for(i=0,last_hyp=hyp_count-1;(last_hyp>0) && (i<point_count);i+=chunk_size)
+        {
+            /*Update cost with the next chunk*/
+            last_corr=db_mini(i+chunk_size-1,point_count-1);
+            for(j=0;j<=last_hyp;j++)
+            {
+                hyp_point=hyp_H_array+9*hyp_perm[j];
+                for(c=i;c<=last_corr;)
+                {
+                    /*Take log of product of ten reprojection
+                    errors to reduce nr of expensive log operations*/
+                    if(c+9<=last_corr)
+                    {
+                        x_i_temp=x_i+(c<<1);
+                        xp_i_temp=xp_i+(c<<1);
+
+                        acc=db_ExpCauchyInhomogenousHomographyError(xp_i_temp,hyp_point,x_i_temp,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+2,hyp_point,x_i_temp+2,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+4,hyp_point,x_i_temp+4,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+6,hyp_point,x_i_temp+6,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+8,hyp_point,x_i_temp+8,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+10,hyp_point,x_i_temp+10,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+12,hyp_point,x_i_temp+12,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+14,hyp_point,x_i_temp+14,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+16,hyp_point,x_i_temp+16,one_over_scale2);
+                        acc*=db_ExpCauchyInhomogenousHomographyError(xp_i_temp+18,hyp_point,x_i_temp+18,one_over_scale2);
+                        c+=10;
+                    }
+                    else
+                    {
+                        for(acc=1.0;c<=last_corr;c++)
+                        {
+                            acc*=db_ExpCauchyInhomogenousHomographyError(xp_i+(c<<1),hyp_point,x_i+(c<<1),one_over_scale2);
+                        }
+                    }
+                    hyp_cost_array[j]+=log(acc);
+                }
+            }
+            if (chunk_size<point_count){
+                /*Prune out half of the hypotheses*/
+                new_last_hyp=(last_hyp+1)/2-1;
+                pivot=db_LeanQuickSelect(hyp_cost_array,last_hyp+1,new_last_hyp,temp_select);
+                for(j=0,c=0;(j<=last_hyp) && (c<=new_last_hyp);j++)
+                {
+                    if(hyp_cost_array[j]<=pivot)
+                    {
+                        hyp_cost_array[c]=hyp_cost_array[j];
+                        hyp_perm[c]=hyp_perm[j];
+                        c++;
+                    }
+                }
+                last_hyp=new_last_hyp;
+            }
+        }
+        /*Find the best hypothesis*/
+        lowest_cost=hyp_cost_array[0];
+        best_pos=0;
+        for(j=1;j<=last_hyp;j++)
+        {
+            if(hyp_cost_array[j]<lowest_cost)
+            {
+                lowest_cost=hyp_cost_array[j];
+                best_pos=j;
+            }
+        }
+
+        /*Move the best hypothesis*/
+        db_Copy9(H_temp,hyp_H_array+9*hyp_perm[best_pos]);
+
+        // outlier removal
+        if (outlierremoveflagE) // no polishment needed
+        {
+            point_count_new = db_RemoveOutliers_Homography(H_temp,x_i,xp_i,wp,im,im_p,im_r,im_raw,im_raw_p,point_count,one_over_scale2);
+        }
+        else
+        {
+            /*Polish*/
+            switch(homography_type)
+            {
+            case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+            case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+            case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+            case DB_HOMOGRAPHY_TYPE_ROTATION:
+            case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+            case DB_HOMOGRAPHY_TYPE_SCALING:
+            case DB_HOMOGRAPHY_TYPE_S_T:
+            case DB_HOMOGRAPHY_TYPE_R_T:
+            case DB_HOMOGRAPHY_TYPE_R_S:
+            case DB_HOMOGRAPHY_TYPE_AFFINE:
+            case DB_HOMOGRAPHY_TYPE_PROJECTIVE:
+            case DB_HOMOGRAPHY_TYPE_CAMROTATION_F:
+            case DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD:
+                db_RobCamRotation_Polish_Generic(H_temp,db_mini(point_count,max_points),homography_type,x_i,xp_i,one_over_scale2,max_iterations);
+                break;
+            case DB_HOMOGRAPHY_TYPE_CAMROTATION:
+                db_RobCamRotation_Polish(H_temp,db_mini(point_count,max_points),x_i,xp_i,one_over_scale2,max_iterations);
+                break;
+            }
+
+        }
+
+    }
+    else db_Identity3x3(H_temp);
+
+    switch(homography_type)
+    {
+    case DB_HOMOGRAPHY_TYPE_PROJECTIVE:
+        if(stat) stat->nr_parameters=8;
+        break;
+    case DB_HOMOGRAPHY_TYPE_AFFINE:
+        if(stat) stat->nr_parameters=6;
+        break;
+    case DB_HOMOGRAPHY_TYPE_SIMILARITY:
+    case DB_HOMOGRAPHY_TYPE_SIMILARITY_U:
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION_F:
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD:
+        if(stat) stat->nr_parameters=4;
+        break;
+    case DB_HOMOGRAPHY_TYPE_CAMROTATION:
+        if(stat) stat->nr_parameters=3;
+        break;
+    case DB_HOMOGRAPHY_TYPE_TRANSLATION:
+    case DB_HOMOGRAPHY_TYPE_S_T:
+    case DB_HOMOGRAPHY_TYPE_R_T:
+    case DB_HOMOGRAPHY_TYPE_R_S:
+        if(stat) stat->nr_parameters=2;
+        break;
+    case DB_HOMOGRAPHY_TYPE_ROTATION:
+    case DB_HOMOGRAPHY_TYPE_ROTATION_U:
+    case DB_HOMOGRAPHY_TYPE_SCALING:
+        if(stat) stat->nr_parameters=1;
+        break;
+    }
+
+    db_RobImageHomography_Statistics(H_temp,db_mini(point_count,max_points),x_i,xp_i,one_over_scale2,stat);
+
+    /*Put on the calibration matrices*/
+    db_Multiply3x3_3x3(H_temp2,H_temp,K_inv);
+    db_Multiply3x3_3x3(H,Kp,H_temp2);
+
+    if (finalNumE)
+        *finalNumE = point_count_new;
+
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.h b/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.h
new file mode 100644
index 0000000..59cde7d
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_rob_image_homography.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_rob_image_homography.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_ROB_IMAGE_HOMOGRAPHY
+#define DB_ROB_IMAGE_HOMOGRAPHY
+
+#include "db_utilities.h"
+#include "db_robust.h"
+#include "db_metrics.h"
+
+#include <stdlib.h> // for NULL
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMRobImageHomography (LM) Robust Image Homography
+ */
+/*\{*/
+
+#define DB_HOMOGRAPHY_TYPE_DEFAULT           0
+#define DB_HOMOGRAPHY_TYPE_PROJECTIVE        0
+#define DB_HOMOGRAPHY_TYPE_AFFINE            1
+#define DB_HOMOGRAPHY_TYPE_SIMILARITY        2
+#define DB_HOMOGRAPHY_TYPE_SIMILARITY_U      3
+#define DB_HOMOGRAPHY_TYPE_TRANSLATION       4
+#define DB_HOMOGRAPHY_TYPE_ROTATION          5
+#define DB_HOMOGRAPHY_TYPE_ROTATION_U        6
+#define DB_HOMOGRAPHY_TYPE_SCALING           7
+#define DB_HOMOGRAPHY_TYPE_S_T               8
+#define DB_HOMOGRAPHY_TYPE_R_T               9
+#define DB_HOMOGRAPHY_TYPE_R_S              10
+#define DB_HOMOGRAPHY_TYPE_CAMROTATION      11
+#define DB_HOMOGRAPHY_TYPE_CAMROTATION_F    12
+#define DB_HOMOGRAPHY_TYPE_CAMROTATION_F_UD 13
+
+/*!
+Solve for homography H such that xp~Hx
+\param H    best homography
+
+2D point to 2D point constraints:
+
+\param im           first image points
+\param im_p         second image points
+\param nr_points    number of points
+
+Calibration matrices:
+
+\param K    first camera
+\param Kp   second camera
+
+ Temporary space:
+
+ \param temp_d      pre-allocated space of size 12*nr_samples+10*nr_points doubles
+ \param temp_i      pre-allocated space of size max(nr_samples,nr_points) ints
+
+ Statistics for this estimation
+
+ \param stat        NULL - do not compute
+
+ \param homography_type see DB_HOMOGRAPHY_TYPE_* definitions above
+
+ Estimation parameters:
+
+ \param max_iterations  max number of polishing steps
+ \param max_points      only use this many points
+ \param scale           Cauchy scale coefficient (see db_ExpCauchyReprojectionError() )
+ \param nr_samples      number of times to compute a hypothesis
+ \param chunk_size      size of cost chunks
+*/
+DB_API void db_RobImageHomography(
+                              /*Best homography*/
+                              double H[9],
+                              /*2DPoint to 2DPoint constraints
+                              Points are assumed to be given in
+                              homogenous coordinates*/
+                              double *im,double *im_p,
+                              /*Nr of points in total*/
+                              int nr_points,
+                              /*Calibration matrices
+                              used to normalize the points*/
+                              double K[9],
+                              double Kp[9],
+                              /*Pre-allocated space temp_d
+                              should point to at least
+                              12*nr_samples+10*nr_points
+                              allocated positions*/
+                              double *temp_d,
+                              /*Pre-allocated space temp_i
+                              should point to at least
+                              max(nr_samples,nr_points)
+                              allocated positions*/
+                              int *temp_i,
+                              int homography_type=DB_HOMOGRAPHY_TYPE_DEFAULT,
+                              db_Statistics *stat=NULL,
+                              int max_iterations=DB_DEFAULT_MAX_ITERATIONS,
+                              int max_points=DB_DEFAULT_MAX_POINTS,
+                              double scale=DB_POINT_STANDARDDEV,
+                              int nr_samples=DB_DEFAULT_NR_SAMPLES,
+                              int chunk_size=DB_DEFAULT_CHUNK_SIZE,
+                              ///////////////////////////////////////////////////
+                              // flag for the outlier removal
+                              int outlierremoveflagE = 0,
+                              // if flag is 1, then the following variables
+                              // need to input
+                              ///////////////////////////////////////////////////
+                              // 3D coordinates
+                              double *wp=NULL,
+                              // its corresponding stereo pair's points
+                              double *im_r=NULL,
+                              // raw image coordinates
+                              double *im_raw=NULL, double *im_raw_p=NULL,
+                              // final matches
+                              int *final_NumE=0);
+
+DB_API double db_RobImageHomography_Cost(double H[9],int point_count,double *x_i,
+                                                double *xp_i,double one_over_scale2);
+
+
+DB_API void db_RobCamRotation_Polish(double H[9],int point_count,double *x_i,
+                                     double *xp_i, double one_over_scale2,
+                                     int max_iterations=DB_DEFAULT_MAX_ITERATIONS,
+                                     double improvement_requirement=DB_DEFAULT_IMP_REQ);
+
+
+DB_API void db_RobCamRotation_Polish_Generic(double H[9],int point_count,int homography_type,
+                                             double *x_i,double *xp_i,double one_over_scale2,
+                                             int max_iterations=DB_DEFAULT_MAX_ITERATIONS,
+                                             double improvement_requirement=DB_DEFAULT_IMP_REQ);
+
+
+#endif /* DB_ROB_IMAGE_HOMOGRAPHY */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_robust.h b/jni_mosaic/feature_stab/db_vlvm/db_robust.h
new file mode 100644
index 0000000..be0794c
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_robust.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_robust.h,v 1.4 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_ROBUST
+#define DB_ROBUST
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMRobust (LM) Robust Estimation
+ */
+
+/*!
+    \struct     db_Statistics
+    \ingroup    LMRobust
+    \brief      (LnM) Sampling problem statistics
+    \date       Mon Sep 10 10:28:08 EDT 2007
+    \par        Copyright: 2007 Sarnoff Corporation.  All Rights Reserved
+ */
+ struct db_stat_struct
+ {
+     int nr_points;
+     int nr_inliers;
+     double inlier_fraction;
+     double cost;
+     double one_over_scale2;
+     double lambda1;
+     double lambda2;
+     double lambda3;
+     int nr_parameters;
+     int model_dimension;
+     double gric;
+     double inlier_evidence;
+     double posestd[6];
+     double rotationvecCov[9];
+     double translationvecCov[9];
+     int posecov_inliercount;
+     int posecovready;
+     double median_reprojection_error;
+ };
+ typedef db_stat_struct db_Statistics;
+
+#endif /* DB_ROBUST */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities.cpp b/jni_mosaic/feature_stab/db_vlvm/db_utilities.cpp
new file mode 100644
index 0000000..ce2093b
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities.cpp,v 1.4 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities.h"
+#include <string.h>
+#include <stdio.h>
+
+float** db_SetupImageReferences_f(float *im,int w,int h)
+{
+    int i;
+    float **img;
+    assert(im);
+    img=new float* [h];
+    for(i=0;i<h;i++)
+    {
+        img[i]=im+w*i;
+    }
+    return(img);
+}
+
+unsigned char** db_SetupImageReferences_u(unsigned char *im,int w,int h)
+{
+    int i;
+    unsigned char **img;
+
+    assert(im);
+
+    img=new unsigned char* [h];
+    for(i=0;i<h;i++)
+    {
+        img[i]=im+w*i;
+    }
+    return(img);
+}
+float** db_AllocImage_f(int w,int h,int over_allocation)
+{
+    float **img,*im;
+
+    im=new float [w*h+over_allocation];
+    img=db_SetupImageReferences_f(im,w,h);
+
+    return(img);
+}
+
+unsigned char** db_AllocImage_u(int w,int h,int over_allocation)
+{
+    unsigned char **img,*im;
+
+    im=new unsigned char [w*h+over_allocation];
+    img=db_SetupImageReferences_u(im,w,h);
+
+    return(img);
+}
+
+void db_FreeImage_f(float **img,int h)
+{
+    delete [] (img[0]);
+    delete [] img;
+}
+
+void db_FreeImage_u(unsigned char **img,int h)
+{
+    delete [] (img[0]);
+    delete [] img;
+}
+
+// ----------------------------------------------------------------------------------------------------------- ;
+//
+// copy image (source to destination)
+// ---> must be a 2D image array with the same image size
+// ---> the size of the input and output images must be same
+//
+// ------------------------------------------------------------------------------------------------------------ ;
+void db_CopyImage_u(unsigned char **d,const unsigned char * const *s, int w, int h, int over_allocation)
+{
+    int i;
+
+    for (i=0;i<h;i++)
+    {
+        memcpy(d[i],s[i],w*sizeof(unsigned char));
+    }
+
+    memcpy(&d[h],&d[h],over_allocation);
+
+}
+
+inline void db_WarpImageLutFast_u(const unsigned char * const * src, unsigned char ** dst, int w, int h,
+                                  const float * const * lut_x, const float * const * lut_y)
+{
+    assert(src && dst);
+    int xd=0, yd=0;
+
+    for ( int i = 0; i < w; ++i )
+        for ( int j = 0; j < h; ++j )
+        {
+            //xd = static_cast<unsigned int>(lut_x[j][i]);
+            //yd = static_cast<unsigned int>(lut_y[j][i]);
+            xd = (unsigned int)(lut_x[j][i]);
+            yd = (unsigned int)(lut_y[j][i]);
+            if ( xd >= w || yd >= h ||
+                 xd < 0 || yd < 0)
+                dst[j][i] = 0;
+            else
+                dst[j][i] = src[yd][xd];
+        }
+}
+
+inline void db_WarpImageLutBilinear_u(const unsigned char * const * src, unsigned char ** dst, int w, int h,
+                                      const float * const * lut_x,const float * const* lut_y)
+{
+    assert(src && dst);
+    double xd=0.0, yd=0.0;
+
+    for ( int i = 0; i < w; ++i )
+        for ( int j = 0; j < h; ++j )
+        {
+            xd = static_cast<double>(lut_x[j][i]);
+            yd = static_cast<double>(lut_y[j][i]);
+            if ( xd > w   || yd > h ||
+                 xd < 0.0 || yd < 0.0)
+                dst[j][i] = 0;
+            else
+                dst[j][i] = db_BilinearInterpolation(yd, xd, src);
+        }
+}
+
+
+void db_WarpImageLut_u(const unsigned char * const * src, unsigned char ** dst, int w, int h,
+                       const float * const * lut_x,const float * const * lut_y, int type)
+{
+    switch (type)
+    {
+    case DB_WARP_FAST:
+        db_WarpImageLutFast_u(src,dst,w,h,lut_x,lut_y);
+        break;
+    case DB_WARP_BILINEAR:
+        db_WarpImageLutBilinear_u(src,dst,w,h,lut_x,lut_y);
+        break;
+    default:
+        break;
+    }
+}
+
+
+void db_PrintDoubleVector(double *a,long size)
+{
+    printf("[ ");
+    for(long i=0;i<size;i++) printf("%lf ",a[i]);
+    printf("]");
+}
+
+void db_PrintDoubleMatrix(double *a,long rows,long cols)
+{
+    printf("[\n");
+    for(long i=0;i<rows;i++)
+    {
+        for(long j=0;j<cols;j++) printf("%lf ",a[i*cols+j]);
+        printf("\n");
+    }
+    printf("]");
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities.h
new file mode 100644
index 0000000..fa9c877
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities.h
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_H
+#define DB_UTILITIES_H
+
+
+#ifdef _WIN32
+#pragma warning(disable: 4275)
+#pragma warning(disable: 4251)
+#pragma warning(disable: 4786)
+#pragma warning(disable: 4800)
+#pragma warning(disable: 4018) /* signed-unsigned mismatch */
+#endif /* _WIN32 */
+
+#ifdef _WIN32
+    #ifdef DBDYNAMIC_EXPORTS
+        #define DB_API __declspec(dllexport)
+    #else
+        #ifdef DBDYNAMIC_IMPORTS
+            #define DB_API __declspec(dllimport)
+        #else
+            #define DB_API
+        #endif
+    #endif
+#else
+    #define DB_API
+#endif /* _WIN32 */
+
+#ifdef _VERBOSE_
+#include <iostream>
+#endif
+
+#include <math.h>
+
+#include <assert.h>
+#include "db_utilities_constants.h"
+/*!
+ * \defgroup LMBasicUtilities (LM) Utility Functions (basic math, linear algebra and array manipulations)
+ */
+/*\{*/
+
+/*!
+ * Round double into int using fld and fistp instructions.
+ */
+inline int db_roundi (double x) {
+#ifdef WIN32_ASM
+    int n;
+    __asm
+    {
+        fld x;
+        fistp n;
+    }
+    return n;
+#else
+    return static_cast<int>(floor(x+0.5));
+#endif
+}
+
+/*!
+ * Square a double.
+ */
+inline double db_sqr(double a)
+{
+    return(a*a);
+}
+
+/*!
+ * Square a long.
+ */
+inline long db_sqr(long a)
+{
+    return(a*a);
+}
+
+/*!
+ * Square an int.
+ */
+inline long db_sqr(int a)
+{
+    return(a*a);
+}
+
+/*!
+ * Maximum of two doubles.
+ */
+inline double db_maxd(double a,double b)
+{
+    if(b>a) return(b);
+    else return(a);
+}
+/*!
+ * Minumum of two doubles.
+ */
+inline double db_mind(double a,double b)
+{
+    if(b<a) return(b);
+    else return(a);
+}
+
+
+/*!
+ * Maximum of two ints.
+ */
+inline int db_maxi(int a,int b)
+{
+    if(b>a) return(b);
+    else return(a);
+}
+
+/*!
+ * Minimum of two numbers.
+ */
+inline int db_mini(int a,int b)
+{
+    if(b<a) return(b);
+    else return(a);
+}
+/*!
+ * Maximum of two numbers.
+ */
+inline long db_maxl(long a,long b)
+{
+    if(b>a) return(b);
+    else return(a);
+}
+
+/*!
+ * Minimum of two numbers.
+ */
+inline long db_minl(long a,long b)
+{
+    if(b<a) return(b);
+    else return(a);
+}
+
+/*!
+ * Sign of a number.
+ * \return -1.0 if negative, 1.0 if positive.
+ */
+inline double db_sign(double x)
+{
+    if(x>=0.0) return(1.0);
+    else return(-1.0);
+}
+/*!
+ * Absolute value.
+ */
+inline int db_absi(int a)
+{
+    if(a<0) return(-a);
+    else return(a);
+}
+/*!
+ * Absolute value.
+ */
+inline float db_absf(float a)
+{
+    if(a<0) return(-a);
+    else return(a);
+}
+
+/*!
+ * Absolute value.
+ */
+inline double db_absd(double a)
+{
+    if(a<0) return(-a);
+    else return(a);
+}
+
+/*!
+ * Reciprocal (1/a). Prevents divide by 0.
+ * \return 1/a if a != 0. 1.0 otherwise.
+ */
+inline double db_SafeReciprocal(double a)
+{
+    return((a!=0.0)?(1.0/a):1.0);
+}
+
+/*!
+ * Division. Prevents divide by 0.
+ * \return a/b if b!=0. a otherwise.
+ */
+inline double db_SafeDivision(double a,double b)
+{
+    return((b!=0.0)?(a/b):a);
+}
+
+/*!
+ * Square root. Prevents imaginary output.
+ * \return sqrt(a) if a > 0.0. 0.0 otherewise.
+ */
+inline double db_SafeSqrt(double a)
+{
+    return((a>=0.0)?(sqrt(a)):0.0);
+}
+
+/*!
+ * Square root of a reciprocal. Prevents divide by 0 and imaginary output.
+ * \return sqrt(1/a) if a > 0.0. 1.0 otherewise.
+ */
+inline double db_SafeSqrtReciprocal(double a)
+{
+    return((a>0.0)?(sqrt(1.0/a)):1.0);
+}
+/*!
+ * Cube root.
+ */
+inline double db_CubRoot(double x)
+{
+    if(x>=0.0) return(pow(x,1.0/3.0));
+    else return(-pow(-x,1.0/3.0));
+}
+/*!
+ * Sum of squares of elements of x.
+ */
+inline double db_SquareSum3(const double x[3])
+{
+    return(db_sqr(x[0])+db_sqr(x[1])+db_sqr(x[2]));
+}
+/*!
+ * Sum of squares of elements of x.
+ */
+inline double db_SquareSum7(double x[7])
+{
+    return(db_sqr(x[0])+db_sqr(x[1])+db_sqr(x[2])+
+        db_sqr(x[3])+db_sqr(x[4])+db_sqr(x[5])+
+        db_sqr(x[6]));
+}
+/*!
+ * Sum of squares of elements of x.
+ */
+inline double db_SquareSum9(double x[9])
+{
+    return(db_sqr(x[0])+db_sqr(x[1])+db_sqr(x[2])+
+        db_sqr(x[3])+db_sqr(x[4])+db_sqr(x[5])+
+        db_sqr(x[6])+db_sqr(x[7])+db_sqr(x[8]));
+}
+/*!
+ * Copy a vector.
+ * \param xd destination
+ * \param xs source
+ */
+void inline db_Copy3(double xd[3],const double xs[3])
+{
+    xd[0]=xs[0];xd[1]=xs[1];xd[2]=xs[2];
+}
+/*!
+ * Copy a vector.
+ * \param xd destination
+ * \param xs source
+ */
+void inline db_Copy6(double xd[6],const double xs[6])
+{
+    xd[0]=xs[0];xd[1]=xs[1];xd[2]=xs[2];
+    xd[3]=xs[3];xd[4]=xs[4];xd[5]=xs[5];
+}
+/*!
+ * Copy a vector.
+ * \param xd destination
+ * \param xs source
+ */
+void inline db_Copy9(double xd[9],const double xs[9])
+{
+    xd[0]=xs[0];xd[1]=xs[1];xd[2]=xs[2];
+    xd[3]=xs[3];xd[4]=xs[4];xd[5]=xs[5];
+    xd[6]=xs[6];xd[7]=xs[7];xd[8]=xs[8];
+}
+
+/*!
+ * Scalar product: Transpose(A)*B.
+ */
+inline double db_ScalarProduct4(const double A[4],const double B[4])
+{
+    return(A[0]*B[0]+A[1]*B[1]+A[2]*B[2]+A[3]*B[3]);
+}
+/*!
+ * Scalar product: Transpose(A)*B.
+ */
+inline double db_ScalarProduct7(const double A[7],const double B[7])
+{
+    return(A[0]*B[0]+A[1]*B[1]+A[2]*B[2]+
+        A[3]*B[3]+A[4]*B[4]+A[5]*B[5]+
+        A[6]*B[6]);
+}
+/*!
+ * Scalar product: Transpose(A)*B.
+ */
+inline double db_ScalarProduct9(const double A[9],const double B[9])
+{
+    return(A[0]*B[0]+A[1]*B[1]+A[2]*B[2]+
+        A[3]*B[3]+A[4]*B[4]+A[5]*B[5]+
+        A[6]*B[6]+A[7]*B[7]+A[8]*B[8]);
+}
+/*!
+ * Vector addition: S=A+B.
+ */
+inline void db_AddVectors6(double S[6],const double A[6],const double B[6])
+{
+    S[0]=A[0]+B[0]; S[1]=A[1]+B[1]; S[2]=A[2]+B[2]; S[3]=A[3]+B[3]; S[4]=A[4]+B[4];
+    S[5]=A[5]+B[5];
+}
+/*!
+ * Multiplication: C(3x1)=A(3x3)*B(3x1).
+ */
+inline void db_Multiply3x3_3x1(double y[3],const double A[9],const double x[3])
+{
+    y[0]=A[0]*x[0]+A[1]*x[1]+A[2]*x[2];
+    y[1]=A[3]*x[0]+A[4]*x[1]+A[5]*x[2];
+    y[2]=A[6]*x[0]+A[7]*x[1]+A[8]*x[2];
+}
+inline void db_Multiply3x3_3x3(double C[9], const double A[9],const double B[9])
+{
+    C[0]=A[0]*B[0]+A[1]*B[3]+A[2]*B[6];
+    C[1]=A[0]*B[1]+A[1]*B[4]+A[2]*B[7];
+    C[2]=A[0]*B[2]+A[1]*B[5]+A[2]*B[8];
+
+    C[3]=A[3]*B[0]+A[4]*B[3]+A[5]*B[6];
+    C[4]=A[3]*B[1]+A[4]*B[4]+A[5]*B[7];
+    C[5]=A[3]*B[2]+A[4]*B[5]+A[5]*B[8];
+
+    C[6]=A[6]*B[0]+A[7]*B[3]+A[8]*B[6];
+    C[7]=A[6]*B[1]+A[7]*B[4]+A[8]*B[7];
+    C[8]=A[6]*B[2]+A[7]*B[5]+A[8]*B[8];
+}
+/*!
+ * Multiplication: C(4x1)=A(4x4)*B(4x1).
+ */
+inline void db_Multiply4x4_4x1(double y[4],const double A[16],const double x[4])
+{
+    y[0]=A[0]*x[0]+A[1]*x[1]+A[2]*x[2]+A[3]*x[3];
+    y[1]=A[4]*x[0]+A[5]*x[1]+A[6]*x[2]+A[7]*x[3];
+    y[2]=A[8]*x[0]+A[9]*x[1]+A[10]*x[2]+A[11]*x[3];
+    y[3]=A[12]*x[0]+A[13]*x[1]+A[14]*x[2]+A[15]*x[3];
+}
+/*!
+ * Scalar multiplication in place: A(3)=mult*A(3).
+ */
+inline void db_MultiplyScalar3(double *A,double mult)
+{
+    (*A++) *= mult; (*A++) *= mult; (*A++) *= mult;
+}
+
+/*!
+ * Scalar multiplication: A(3)=mult*B(3).
+ */
+inline void db_MultiplyScalarCopy3(double *A,const double *B,double mult)
+{
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+}
+
+/*!
+ * Scalar multiplication: A(4)=mult*B(4).
+ */
+inline void db_MultiplyScalarCopy4(double *A,const double *B,double mult)
+{
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+}
+/*!
+ * Scalar multiplication: A(7)=mult*B(7).
+ */
+inline void db_MultiplyScalarCopy7(double *A,const double *B,double mult)
+{
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+}
+/*!
+ * Scalar multiplication: A(9)=mult*B(9).
+ */
+inline void db_MultiplyScalarCopy9(double *A,const double *B,double mult)
+{
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+    (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult; (*A++)=(*B++)*mult;
+}
+
+/*!
+ * \defgroup LMImageBasicUtilities (LM) Basic Image Utility Functions
+
+ Images in db are simply 2D arrays of unsigned char or float types.
+ Only the very basic operations are supported: allocation/deallocation,
+copying, simple pyramid construction and LUT warping. These images are used
+by db_CornerDetector_u and db_Matcher_u. The db_Image class is an attempt
+to wrap these images. It has not been tested well.
+
+ */
+/*\{*/
+/*!
+ * Given a float image array, allocates and returns the set of row poiners.
+ * \param im    image pointer
+ * \param w     image width
+ * \param h     image height
+ */
+DB_API float** db_SetupImageReferences_f(float *im,int w,int h);
+/*!
+ * Allocate a float image.
+ * Note: for feature detection images must be overallocated by 256 bytes.
+ * \param w                 width
+ * \param h                 height
+ * \param over_allocation   allocate this many extra bytes at the end
+ * \return row array pointer
+ */
+DB_API float** db_AllocImage_f(int w,int h,int over_allocation=256);
+/*!
+ * Free a float image
+ * \param img   row array pointer
+ * \param h     image height (number of rows)
+ */
+DB_API void db_FreeImage_f(float **img,int h);
+/*!
+ * Given an unsigned char image array, allocates and returns the set of row poiners.
+ * \param im    image pointer
+ * \param w     image width
+ * \param h     image height
+ */
+DB_API unsigned char** db_SetupImageReferences_u(unsigned char *im,int w,int h);
+/*!
+ * Allocate an unsigned char image.
+ * Note: for feature detection images must be overallocated by 256 bytes.
+ * \param w                 width
+ * \param h                 height
+ * \param over_allocation   allocate this many extra bytes at the end
+ * \return row array pointer
+ */
+DB_API unsigned char** db_AllocImage_u(int w,int h,int over_allocation=256);
+/*!
+ * Free an unsigned char image
+ * \param img   row array pointer
+ * \param h     image height (number of rows)
+ */
+DB_API void db_FreeImage_u(unsigned char **img,int h);
+
+/*!
+ Copy an image from s to d. Both s and d must be pre-allocated at of the same size.
+ Copy is done row by row.
+ \param s   source
+ \param d   destination
+ \param w   width
+ \param h   height
+ \param over_allocation copy this many bytes after the end of the last line
+ */
+DB_API void db_CopyImage_u(unsigned char **d,const unsigned char * const *s,int w,int h,int over_allocation=0);
+
+DB_API inline unsigned char db_BilinearInterpolation(double y, double x, const unsigned char * const * v)
+{
+    int floor_x=(int) x;
+    int floor_y=(int) y;
+
+    int ceil_x=floor_x+1;
+    int ceil_y=floor_y+1;
+
+    unsigned char f00 = v[floor_y][floor_x];
+    unsigned char f01 = v[floor_y][ceil_x];
+    unsigned char f10 = v[ceil_y][floor_x];
+    unsigned char f11 = v[ceil_y][ceil_x];
+
+    double xl = x-floor_x;
+    double yl = y-floor_y;
+
+    return (unsigned char)(f00*(1-yl)*(1-xl) + f10*yl*(1-xl) + f01*(1-yl)*xl + f11*yl*xl);
+}
+/*\}*/
+/*!
+ * \ingroup LMRotation
+ * Compute an incremental rotation matrix using the update dx=[sin(phi) sin(ohm) sin(kap)]
+ */
+inline void db_IncrementalRotationMatrix(double R[9],const double dx[3])
+{
+    double sp,so,sk,om_sp2,om_so2,om_sk2,cp,co,ck,sp_so,cp_so;
+
+    /*Store sines*/
+    sp=dx[0]; so=dx[1]; sk=dx[2];
+    om_sp2=1.0-sp*sp;
+    om_so2=1.0-so*so;
+    om_sk2=1.0-sk*sk;
+    /*Compute cosines*/
+    cp=(om_sp2>=0.0)?sqrt(om_sp2):1.0;
+    co=(om_so2>=0.0)?sqrt(om_so2):1.0;
+    ck=(om_sk2>=0.0)?sqrt(om_sk2):1.0;
+    /*Compute matrix*/
+    sp_so=sp*so;
+    cp_so=cp*so;
+    R[0]=sp_so*sk+cp*ck; R[1]=co*sk; R[2]=cp_so*sk-sp*ck;
+    R[3]=sp_so*ck-cp*sk; R[4]=co*ck; R[5]=cp_so*ck+sp*sk;
+    R[6]=sp*co;          R[7]= -so;  R[8]=cp*co;
+}
+/*!
+ * Zero out 2 vector in place.
+ */
+void inline db_Zero2(double x[2])
+{
+    x[0]=x[1]=0;
+}
+/*!
+ * Zero out 3 vector in place.
+ */
+void inline db_Zero3(double x[3])
+{
+    x[0]=x[1]=x[2]=0;
+}
+/*!
+ * Zero out 4 vector in place.
+ */
+void inline db_Zero4(double x[4])
+{
+    x[0]=x[1]=x[2]=x[3]=0;
+}
+/*!
+ * Zero out 9 vector in place.
+ */
+void inline db_Zero9(double x[9])
+{
+    x[0]=x[1]=x[2]=x[3]=x[4]=x[5]=x[6]=x[7]=x[8]=0;
+}
+
+#define DB_WARP_FAST        0
+#define DB_WARP_BILINEAR    1
+
+/*!
+ * Perform a look-up table warp.
+ * The LUTs must be float images of the same size as source image.
+ * The source value x_s is determined from destination (x_d,y_d) through lut_x
+ * and y_s is determined from lut_y:
+   \code
+   x_s = lut_x[y_d][x_d];
+   y_s = lut_y[y_d][x_d];
+   \endcode
+
+ * \param src   source image
+ * \param dst   destination image
+ * \param w     width
+ * \param h     height
+ * \param lut_x LUT for x
+ * \param lut_y LUT for y
+ * \param type  warp type (DB_WARP_FAST or DB_WARP_BILINEAR)
+ */
+DB_API void db_WarpImageLut_u(const unsigned char * const * src,unsigned char ** dst, int w, int h,
+                               const float * const * lut_x, const float * const * lut_y, int type=DB_WARP_BILINEAR);
+
+DB_API void db_PrintDoubleVector(double *a,long size);
+DB_API void db_PrintDoubleMatrix(double *a,long rows,long cols);
+
+#include "db_utilities_constants.h"
+#include "db_utilities_algebra.h"
+#include "db_utilities_indexing.h"
+#include "db_utilities_linalg.h"
+#include "db_utilities_poly.h"
+#include "db_utilities_geometry.h"
+#include "db_utilities_random.h"
+#include "db_utilities_rotation.h"
+#include "db_utilities_camera.h"
+
+#define DB_INVALID (-1)
+
+
+#endif /* DB_UTILITIES_H */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_algebra.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_algebra.h
new file mode 100644
index 0000000..2aedd74
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_algebra.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_algebra.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_ALGEBRA
+#define DB_UTILITIES_ALGEBRA
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMAlgebra (LM) Algebra utilities
+ */
+/*\{*/
+
+inline void db_HomogenousNormalize3(double *x)
+{
+    db_MultiplyScalar3(x,db_SafeSqrtReciprocal(db_SquareSum3(x)));
+}
+
+/*\}*/
+
+#endif /* DB_UTILITIES_ALGEBRA */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.cpp b/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.cpp
new file mode 100644
index 0000000..dceba9b
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_camera.cpp,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities_camera.h"
+#include "db_utilities.h"
+#include <assert.h>
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+void db_Approx3DCalMat(double K[9],double Kinv[9],int im_width,int im_height,double f_correction,int field)
+{
+    double iw,ih,av_size,field_fact;
+
+    if(field) field_fact=2.0;
+    else field_fact=1.0;
+
+    iw=(double)im_width;
+    ih=(double)(im_height*field_fact);
+    av_size=(iw+ih)/2.0;
+    K[0]=f_correction*av_size;
+    K[1]=0;
+    K[2]=iw/2.0;
+    K[3]=0;
+    K[4]=f_correction*av_size/field_fact;
+    K[5]=ih/2.0/field_fact;
+    K[6]=0;
+    K[7]=0;
+    K[8]=1;
+
+    db_InvertCalibrationMatrix(Kinv,K);
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.h
new file mode 100644
index 0000000..26ba442
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_camera.h
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_camera.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_CAMERA
+#define DB_UTILITIES_CAMERA
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMCamera (LM) Camera Utilities
+ */
+/*\{*/
+
+#include "db_utilities.h"
+
+#define DB_RADDISTMODE_BOUGEUT  4
+#define DB_RADDISTMODE_2NDORDER 5
+#define DB_RADDISTMODE_IDENTITY 6
+
+/*!
+Give reasonable guess of the calibration matrix for normalization purposes.
+Use real K matrix when doing real geometry.
+focal length = (w+h)/2.0*f_correction.
+\param K            calibration matrix (out)
+\param Kinv         inverse of K (out)
+\param im_width     image width
+\param im_height    image height
+\param f_correction focal length correction factor
+\param field        set to 1 if this is a field image (fy = fx/2)
+\return K(3x3) intrinsic calibration matrix
+*/
+DB_API void db_Approx3DCalMat(double K[9],double Kinv[9],int im_width,int im_height,double f_correction=1.0,int field=0);
+
+/*!
+ Make a 2x2 identity matrix
+ */
+void inline db_Identity2x2(double A[4])
+{
+    A[0]=1;A[1]=0;
+    A[2]=0;A[3]=1;
+}
+/*!
+ Make a 3x3 identity matrix
+ */
+void inline db_Identity3x3(double A[9])
+{
+    A[0]=1;A[1]=0;A[2]=0;
+    A[3]=0;A[4]=1;A[5]=0;
+    A[6]=0;A[7]=0;A[8]=1;
+}
+/*!
+ Invert intrinsic calibration matrix K(3x3)
+ If fx or fy is 0, I is returned.
+ */
+void inline db_InvertCalibrationMatrix(double Kinv[9],const double K[9])
+{
+    double a,b,c,d,e,f,ainv,dinv,adinv;
+
+    a=K[0];b=K[1];c=K[2];d=K[4];e=K[5];f=K[8];
+    if((a==0.0)||(d==0.0)) db_Identity3x3(Kinv);
+    else
+    {
+        Kinv[3]=0.0;
+        Kinv[6]=0.0;
+        Kinv[7]=0.0;
+        Kinv[8]=1.0;
+
+        ainv=1.0/a;
+        dinv=1.0/d;
+        adinv=ainv*dinv;
+        Kinv[0]=f*ainv;
+        Kinv[1]= -b*f*adinv;
+        Kinv[2]=(b*e-c*d)*adinv;
+        Kinv[4]=f*dinv;
+        Kinv[5]= -e*dinv;
+    }
+}
+/*!
+ De-homogenize image point: xd(1:2) = xs(1:2)/xs(3).
+ If xs(3) is 0, xd will become 0
+ \param xd  destination point
+ \param xs  source point
+ */
+void inline db_DeHomogenizeImagePoint(double xd[2],const double xs[3])
+{
+    double temp,div;
+
+    temp=xs[2];
+    if(temp!=0)
+    {
+        div=1.0/temp;
+        xd[0]=xs[0]*div;xd[1]=xs[1]*div;
+    }
+    else
+    {
+        xd[0]=0.0;xd[1]=0.0;
+    }
+}
+
+
+/*!
+ Orthonormalize 3D rotation R
+ */
+inline void db_OrthonormalizeRotation(double R[9])
+{
+    double s,mult;
+    /*Normalize first vector*/
+    s=db_sqr(R[0])+db_sqr(R[1])+db_sqr(R[2]);
+    mult=sqrt(1.0/(s?s:1));
+    R[0]*=mult; R[1]*=mult; R[2]*=mult;
+    /*Subtract scalar product from second vector*/
+    s=R[0]*R[3]+R[1]*R[4]+R[2]*R[5];
+    R[3]-=s*R[0]; R[4]-=s*R[1]; R[5]-=s*R[2];
+    /*Normalize second vector*/
+    s=db_sqr(R[3])+db_sqr(R[4])+db_sqr(R[5]);
+    mult=sqrt(1.0/(s?s:1));
+    R[3]*=mult; R[4]*=mult; R[5]*=mult;
+    /*Get third vector by vector product*/
+    R[6]=R[1]*R[5]-R[4]*R[2];
+    R[7]=R[2]*R[3]-R[5]*R[0];
+    R[8]=R[0]*R[4]-R[3]*R[1];
+}
+/*!
+Update a rotation with the update dx=[sin(phi) sin(ohm) sin(kap)]
+*/
+inline void db_UpdateRotation(double R_p_dx[9],double R[9],const double dx[3])
+{
+    double R_temp[9];
+    /*Update rotation*/
+    db_IncrementalRotationMatrix(R_temp,dx);
+    db_Multiply3x3_3x3(R_p_dx,R_temp,R);
+}
+/*!
+ Compute xp = Hx for inhomogenous image points.
+ */
+inline void db_ImageHomographyInhomogenous(double xp[2],const double H[9],const double x[2])
+{
+    double x3,m;
+
+    x3=H[6]*x[0]+H[7]*x[1]+H[8];
+    if(x3!=0.0)
+    {
+        m=1.0/x3;
+        xp[0]=m*(H[0]*x[0]+H[1]*x[1]+H[2]);
+        xp[1]=m*(H[3]*x[0]+H[4]*x[1]+H[5]);
+    }
+    else
+    {
+        xp[0]=xp[1]=0.0;
+    }
+}
+inline double db_FocalFromCamRotFocalHomography(const double H[9])
+{
+    double k1,k2;
+
+    k1=db_sqr(H[2])+db_sqr(H[5]);
+    k2=db_sqr(H[6])+db_sqr(H[7]);
+    if(k1>=k2)
+    {
+        return(db_SafeSqrt(db_SafeDivision(k1,1.0-db_sqr(H[8]))));
+    }
+    else
+    {
+        return(db_SafeSqrt(db_SafeDivision(1.0-db_sqr(H[8]),k2)));
+    }
+}
+
+inline double db_FocalAndRotFromCamRotFocalHomography(double R[9],const double H[9])
+{
+    double back,fi;
+
+    back=db_FocalFromCamRotFocalHomography(H);
+    fi=db_SafeReciprocal(back);
+    R[0]=H[0];      R[1]=H[1];      R[2]=fi*H[2];
+    R[3]=H[3];      R[4]=H[4];      R[5]=fi*H[5];
+    R[6]=back*H[6]; R[7]=back*H[7]; R[8]=H[8];
+    return(back);
+}
+/*!
+Compute Jacobian at zero of three coordinates dR*x with
+respect to the update dR([sin(phi) sin(ohm) sin(kap)]) given x.
+
+The Jacobian at zero of the homogenous coordinates with respect to
+    [sin(phi) sin(ohm) sin(kap)] is
+\code
+    [-rx2   0   rx1 ]
+    [  0   rx2 -rx0 ]
+    [ rx0 -rx1   0  ].
+\endcode
+
+*/
+inline void db_JacobianOfRotatedPointStride(double J[9],const double x[3],int stride)
+{
+    /*The Jacobian at zero of the homogenous coordinates with respect to
+    [sin(phi) sin(ohm) sin(kap)] is
+    [-rx2   0   rx1 ]
+    [  0   rx2 -rx0 ]
+    [ rx0 -rx1   0  ]*/
+
+    J[0]= -x[stride<<1];
+    J[1]=0;
+    J[2]=  x[stride];
+    J[3]=0;
+    J[4]=  x[stride<<1];
+    J[5]= -x[0];
+    J[6]=  x[0];
+    J[7]= -x[stride];
+    J[8]=0;
+}
+/*!
+ Invert an affine (if possible)
+ \param Hinv    inverted matrix
+ \param H       input matrix
+ \return true if success and false if matrix is ill-conditioned (det < 1e-7)
+ */
+inline bool db_InvertAffineTransform(double Hinv[9],const double H[9])
+{
+    double det=H[0]*H[4]-H[3]*H[1];
+    if (det<1e-7)
+    {
+        db_Copy9(Hinv,H);
+        return false;
+    }
+    else
+    {
+        Hinv[0]=H[4]/det;
+        Hinv[1]=-H[1]/det;
+        Hinv[3]=-H[3]/det;
+        Hinv[4]=H[0]/det;
+        Hinv[2]= -Hinv[0]*H[2]-Hinv[1]*H[5];
+        Hinv[5]= -Hinv[3]*H[2]-Hinv[4]*H[5];
+    }
+    return true;
+}
+
+/*!
+Update of upper 2x2 is multiplication by
+\code
+[s 0][ cos(theta) sin(theta)]
+[0 s][-sin(theta) cos(theta)]
+\endcode
+*/
+inline void db_MultiplyScaleOntoImageHomography(double H[9],double s)
+{
+
+    H[0]*=s;
+    H[1]*=s;
+    H[3]*=s;
+    H[4]*=s;
+}
+/*!
+Update of upper 2x2 is multiplication by
+\code
+[s 0][ cos(theta) sin(theta)]
+[0 s][-sin(theta) cos(theta)]
+\endcode
+*/
+inline void db_MultiplyRotationOntoImageHomography(double H[9],double theta)
+{
+    double c,s,H0,H1;
+
+
+    c=cos(theta);
+    s=db_SafeSqrt(1.0-db_sqr(c));
+    H0=  c*H[0]+s*H[3];
+    H[3]= -s*H[0]+c*H[3];
+    H[0]=H0;
+    H1=c*H[1]+s*H[4];
+    H[4]= -s*H[1]+c*H[4];
+    H[1]=H1;
+}
+
+inline void db_UpdateImageHomographyAffine(double H_p_dx[9],const double H[9],const double dx[6])
+{
+    db_AddVectors6(H_p_dx,H,dx);
+    db_Copy3(H_p_dx+6,H+6);
+}
+
+inline void db_UpdateImageHomographyProjective(double H_p_dx[9],const double H[9],const double dx[8],int frozen_coord)
+{
+    int i,j;
+
+    for(j=0,i=0;i<9;i++)
+    {
+        if(i!=frozen_coord)
+        {
+            H_p_dx[i]=H[i]+dx[j];
+            j++;
+        }
+        else H_p_dx[i]=H[i];
+    }
+}
+
+inline void db_UpdateRotFocalHomography(double H_p_dx[9],const double H[9],const double dx[4])
+{
+    double f,fp,fpi;
+    double R[9],dR[9];
+
+    /*Updated matrix is diag(f+df,f+df)*dR*R*diag(1/(f+df),1/(f+df),1)*/
+    f=db_FocalAndRotFromCamRotFocalHomography(R,H);
+    db_IncrementalRotationMatrix(dR,dx);
+    db_Multiply3x3_3x3(H_p_dx,dR,R);
+    fp=f+dx[3];
+    fpi=db_SafeReciprocal(fp);
+    H_p_dx[2]*=fp;
+    H_p_dx[5]*=fp;
+    H_p_dx[6]*=fpi;
+    H_p_dx[7]*=fpi;
+}
+
+/*\}*/
+#endif /* DB_UTILITIES_CAMERA */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_constants.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_constants.h
new file mode 100644
index 0000000..07565ef
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_constants.h
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_constants.h,v 1.2 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_CONSTANTS
+#define DB_UTILITIES_CONSTANTS
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+/****************Constants********************/
+#define DB_E             2.7182818284590452354
+#define DB_LOG2E         1.4426950408889634074
+#define DB_LOG10E        0.43429448190325182765
+#define DB_LN2           0.69314718055994530942
+#define DB_LN10          2.30258509299404568402
+#define DB_PI            3.1415926535897932384626433832795
+#define DB_PI_2          1.57079632679489661923
+#define DB_PI_4          0.78539816339744830962
+#define DB_1_PI          0.31830988618379067154
+#define DB_2_PI          0.63661977236758134308
+#define DB_SQRTPI        1.7724538509055160272981674833411
+#define DB_SQRT_2PI      2.506628274631000502415765284811
+#define DB_2_SQRTPI      1.12837916709551257390
+#define DB_SQRT2         1.41421356237309504880
+#define DB_SQRT3         1.7320508075688772935274463415059
+#define DB_SQRT1_2       0.70710678118654752440
+#define DB_EPS           2.220446049250313e-016 /* for 32 bit double */
+
+/****************Default Parameters********************/
+/*Preemptive ransac parameters*/
+#define DB_DEFAULT_NR_SAMPLES 500
+#define DB_DEFAULT_CHUNK_SIZE 100
+#define DB_DEFAULT_GROUP_SIZE 10
+
+/*Optimisation parameters*/
+#define DB_DEFAULT_MAX_POINTS 1000
+#define DB_DEFAULT_MAX_ITERATIONS 25
+#define DB_DEFAULT_IMP_REQ 0.001
+
+/*Feature standard deviation parameters*/
+#define DB_POINT_STANDARDDEV (1.0/(826.0)) /*1 pixel for CIF (fraction of (image width+image height)/2)*/
+#define DB_OUTLIER_THRESHOLD 3.0 /*In number of DB_POINT_STANDARDDEV's*/
+#define DB_WORST_CASE 50.0 /*In number of DB_POINT_STANDARDDEV's*/
+
+/*Front-end parameters*/
+#define DB_DEFAULT_TARGET_NR_CORNERS 5000
+#define DB_DEFAULT_NR_FEATURE_BLOCKS 10
+#define DB_DEFAULT_ABS_CORNER_THRESHOLD 50000000.0
+#define DB_DEFAULT_REL_CORNER_THRESHOLD 0.00005
+#define DB_DEFAULT_MAX_DISPARITY 0.1
+#define DB_DEFAULT_NO_DISPARITY -1.0
+#define DB_DEFAULT_MAX_TRACK_LENGTH 300
+
+#define DB_DEFAULT_MAX_NR_CAMERAS 1000
+
+#define DB_DEFAULT_TRIPLE_STEP 2
+#define DB_DEFAULT_DOUBLE_STEP 2
+#define DB_DEFAULT_SINGLE_STEP 1
+#define DB_DEFAULT_NR_SINGLES 10
+#define DB_DEFAULT_NR_DOUBLES 1
+#define DB_DEFAULT_NR_TRIPLES 1
+
+#define DB_DEFAULT_TRIFOCAL_FOUR_STEPS 40
+
+#define DB_DEFAULT_EPIPOLAR_ERROR 1 /*in pixels*/
+
+////////////////////////// DOXYGEN /////////////////////
+
+/*!
+ * \def DB_DEFAULT_GROUP_SIZE
+ * \ingroup LMRobust
+ * \brief Default group size for db_PreemptiveRansac class.
+ * Group size is the number of observation costs multiplied together
+ * before a log of the product is added to the total cost.
+*/
+
+/*!
+ * \def DB_DEFAULT_TARGET_NR_CORNERS
+ * \ingroup FeatureDetection
+ * \brief Default target number of corners
+*/
+/*!
+ * \def DB_DEFAULT_NR_FEATURE_BLOCKS
+ * \ingroup FeatureDetection
+ * \brief Default number of regions (horizontal or vertical) that are considered separately
+ * for feature detection. The greater the number, the more uniform the distribution of
+ * detected features.
+*/
+/*!
+ * \def DB_DEFAULT_ABS_CORNER_THRESHOLD
+ * \ingroup FeatureDetection
+ * \brief Absolute feature strength threshold.
+*/
+/*!
+ * \def DB_DEFAULT_REL_CORNER_THRESHOLD
+ * \ingroup FeatureDetection
+ * \brief Relative feature strength threshold.
+*/
+/*!
+ * \def DB_DEFAULT_MAX_DISPARITY
+ * \ingroup FeatureMatching
+ * \brief Maximum disparity (as fraction of image size) allowed in feature matching
+*/
+ /*!
+ * \def DB_DEFAULT_NO_DISPARITY
+ * \ingroup FeatureMatching
+ * \brief Indicates that vertical disparity is the same as horizontal disparity.
+*/
+///////////////////////////////////////////////////////////////////////////////////
+ /*!
+ * \def DB_E
+ * \ingroup LMBasicUtilities
+ * \brief e
+*/
+ /*!
+ * \def DB_LOG2E
+ * \ingroup LMBasicUtilities
+ * \brief log2(e)
+*/
+ /*!
+ * \def DB_LOG10E
+ * \ingroup LMBasicUtilities
+ * \brief log10(e)
+*/
+ /*!
+ * \def DB_LOG10E
+ * \ingroup LMBasicUtilities
+ * \brief log10(e)
+*/
+/*!
+ * \def DB_LN2
+ * \ingroup LMBasicUtilities
+ * \brief ln(2)
+*/
+/*!
+ * \def DB_LN10
+ * \ingroup LMBasicUtilities
+ * \brief ln(10)
+*/
+/*!
+ * \def DB_PI
+ * \ingroup LMBasicUtilities
+ * \brief Pi
+*/
+/*!
+ * \def DB_PI_2
+ * \ingroup LMBasicUtilities
+ * \brief Pi/2
+*/
+/*!
+ * \def DB_PI_4
+ * \ingroup LMBasicUtilities
+ * \brief Pi/4
+*/
+/*!
+ * \def DB_1_PI
+ * \ingroup LMBasicUtilities
+ * \brief 1/Pi
+*/
+/*!
+ * \def DB_2_PI
+ * \ingroup LMBasicUtilities
+ * \brief 2/Pi
+*/
+/*!
+ * \def DB_SQRTPI
+ * \ingroup LMBasicUtilities
+ * \brief sqrt(Pi)
+*/
+/*!
+ * \def DB_SQRT_2PI
+ * \ingroup LMBasicUtilities
+ * \brief sqrt(2*Pi)
+*/
+/*!
+ * \def DB_SQRT2
+ * \ingroup LMBasicUtilities
+ * \brief sqrt(2)
+*/
+/*!
+ * \def DB_SQRT3
+ * \ingroup LMBasicUtilities
+ * \brief sqrt(3)
+*/
+/*!
+ * \def DB_SQRT1_2
+ * \ingroup LMBasicUtilities
+ * \brief sqrt(1/2)
+*/
+#endif /* DB_UTILITIES_CONSTANTS */
+
+
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_geometry.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_geometry.h
new file mode 100644
index 0000000..f215584
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_geometry.h
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_geometry.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_GEOMETRY_H
+#define DB_UTILITIES_GEOMETRY_H
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*! Get the inhomogenous 2D-point centroid of nr_point inhomogenous
+points in X*/
+inline void db_PointCentroid2D(double c[2],const double *X,int nr_points)
+{
+    int i;
+    double cx,cy,m;
+
+    cx=0;cy=0;
+    for(i=0;i<nr_points;i++)
+    {
+        cx+= *X++;
+        cy+= *X++;
+    }
+    if(nr_points)
+    {
+        m=1.0/((double)nr_points);
+        c[0]=cx*m;
+        c[1]=cy*m;
+    }
+    else c[0]=c[1]=0;
+}
+
+inline void db_PointCentroid2D(double c[2],const double * const *X,int nr_points)
+{
+    int i;
+    double cx,cy,m;
+    const double *temp;
+
+    cx=0;cy=0;
+    for(i=0;i<nr_points;i++)
+    {
+        temp= *X++;
+        cx+=temp[0];
+        cy+=temp[1];
+    }
+    if(nr_points)
+    {
+        m=1.0/((double)nr_points);
+        c[0]=cx*m;
+        c[1]=cy*m;
+    }
+    else c[0]=c[1]=0;
+}
+
+/*! Get the inhomogenous 3D-point centroid of nr_point inhomogenous
+points in X*/
+inline void db_PointCentroid3D(double c[3],const double *X,int nr_points)
+{
+    int i;
+    double cx,cy,cz,m;
+
+    cx=0;cy=0;cz=0;
+    for(i=0;i<nr_points;i++)
+    {
+        cx+= *X++;
+        cy+= *X++;
+        cz+= *X++;
+    }
+    if(nr_points)
+    {
+        m=1.0/((double)nr_points);
+        c[0]=cx*m;
+        c[1]=cy*m;
+        c[2]=cz*m;
+    }
+    else c[0]=c[1]=c[2]=0;
+}
+
+inline void db_PointCentroid3D(double c[3],const double * const *X,int nr_points)
+{
+    int i;
+    double cx,cy,cz,m;
+    const double *temp;
+
+    cx=0;cy=0;cz=0;
+    for(i=0;i<nr_points;i++)
+    {
+        temp= *X++;
+        cx+=temp[0];
+        cy+=temp[1];
+        cz+=temp[2];
+    }
+    if(nr_points)
+    {
+        m=1.0/((double)nr_points);
+        c[0]=cx*m;
+        c[1]=cy*m;
+        c[2]=cz*m;
+    }
+    else c[0]=c[1]=c[2]=0;
+}
+
+#endif /* DB_UTILITIES_GEOMETRY_H */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.cpp b/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.cpp
new file mode 100644
index 0000000..30ce03a
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_indexing.cpp,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities_indexing.h"
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+void db_Zero(double *d,long nr)
+{
+    long i;
+    for(i=0;i<nr;i++) d[i]=0.0;
+}
+
+/*This routine breaks number in source into values smaller and larger than
+a pivot element. Values equal to the pivot are ignored*/
+void db_LeanPartitionOnPivot(double pivot,double *dest,const double *source,long first,long last,long *first_equal,long *last_equal)
+{
+    double temp;
+    const double *s_point;
+    const double *s_top;
+    double *d_bottom;
+    double *d_top;
+
+    s_point=source+first;
+    s_top=source+last;
+    d_bottom=dest+first;
+    d_top=dest+last;
+
+    for(;s_point<=s_top;)
+    {
+        temp= *(s_point++);
+        if(temp<pivot) *(d_bottom++)=temp;
+        else if(temp>pivot) *(d_top--)=temp;
+    }
+    *first_equal=d_bottom-dest;
+    *last_equal=d_top-dest;
+}
+
+double db_LeanQuickSelect(const double *s,long nr_elements,long pos,double *temp)
+{
+  long first=0;
+  long last=nr_elements-1;
+  double pivot;
+  long first_equal,last_equal;
+  double *tempA;
+  double *tempB;
+  double *tempC;
+  const double *source;
+  double *dest;
+
+  tempA=temp;
+  tempB=temp+nr_elements;
+  source=s;
+  dest=tempA;
+
+  for(;last-first>2;)
+  {
+      pivot=db_TripleMedian(source[first],source[last],source[(first+last)/2]);
+      db_LeanPartitionOnPivot(pivot,dest,source,first,last,&first_equal,&last_equal);
+
+      if(first_equal>pos) last=first_equal-1;
+      else if(last_equal<pos) first=last_equal+1;
+      else
+      {
+        return(pivot);
+      }
+
+      /*Swap pointers*/
+      tempC=tempA;
+      tempA=tempB;
+      tempB=tempC;
+      source=tempB;
+      dest=tempA;
+  }
+  pivot=db_TripleMedian(source[first],source[last],source[(first+last)/2]);
+
+  return(pivot);
+}
+
+float* db_AlignPointer_f(float *p,unsigned long nr_bytes)
+{
+    float *ap;
+    unsigned long m;
+
+    m=((unsigned long)p)%nr_bytes;
+    if(m) ap=(float*) (((unsigned long)p)-m+nr_bytes);
+    else ap=p;
+    return(ap);
+}
+
+short* db_AlignPointer_s(short *p,unsigned long nr_bytes)
+{
+    short *ap;
+    unsigned long m;
+
+    m=((unsigned long)p)%nr_bytes;
+    if(m) ap=(short*) (((unsigned long)p)-m+nr_bytes);
+    else ap=p;
+    return(ap);
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.h
new file mode 100644
index 0000000..01eeb9e
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_indexing.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_indexing.h,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_INDEXING
+#define DB_UTILITIES_INDEXING
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+#include "db_utilities.h"
+
+/*!
+ * \defgroup LMIndexing (LM) Indexing Utilities (Order Statistics, Matrix Operations)
+ */
+/*\{*/
+
+inline void db_SetupMatrixRefs(double **ar,long rows,long cols,double *a)
+{
+    long i;
+    for(i=0;i<rows;i++) ar[i]=&a[i*cols];
+}
+
+inline void db_SymmetricExtendUpperToLower(double **A,int rows,int cols)
+{
+    int i,j;
+    for(i=1;i<rows;i++) for(j=0;j<i;j++) A[i][j]=A[j][i];
+}
+
+void inline db_MultiplyMatrixVectorAtb(double *c,const double * const *At,const double *b,int arows,int acols)
+{
+    int i,j;
+    double acc;
+
+    for(i=0;i<arows;i++)
+    {
+        acc=0;
+        for(j=0;j<acols;j++) acc+=At[j][i]*b[j];
+        c[i]=acc;
+    }
+}
+
+inline void db_MultiplyMatricesAB(double **C,const double * const *A,const double * const *B,int arows,int acols,int bcols)
+{
+    int i,j,k;
+    double acc;
+
+    for(i=0;i<arows;i++) for(j=0;j<bcols;j++)
+    {
+        acc=0;
+        for(k=0;k<acols;k++) acc+=A[i][k]*B[k][j];
+        C[i][j]=acc;
+    }
+}
+
+inline void db_UpperMultiplyMatricesAtB(double **Cu,const double * const *At,const double * const *B,int arows,int acols,int bcols)
+{
+    int i,j,k;
+    double acc;
+
+    for(i=0;i<arows;i++) for(j=i;j<bcols;j++)
+    {
+        acc=0;
+        for(k=0;k<acols;k++) acc+=At[k][i]*B[k][j];
+        Cu[i][j]=acc;
+    }
+}
+
+DB_API void db_Zero(double *d,long nr);
+
+inline int db_MaxIndex2(double s[2])
+{
+    if(s[0]>=s[1]) return(0);
+    return(1);
+}
+
+inline int db_MaxIndex3(const double s[3])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]>best){best=s[1];pos=1;}
+    if(s[2]>best){best=s[2];pos=2;}
+    return(pos);
+}
+
+inline int db_MaxIndex4(const double s[4])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]>best){best=s[1];pos=1;}
+    if(s[2]>best){best=s[2];pos=2;}
+    if(s[3]>best){best=s[3];pos=3;}
+    return(pos);
+}
+
+inline int db_MaxIndex5(const double s[5])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]>best){best=s[1];pos=1;}
+    if(s[2]>best){best=s[2];pos=2;}
+    if(s[3]>best){best=s[3];pos=3;}
+    if(s[4]>best){best=s[4];pos=4;}
+    return(pos);
+}
+
+inline int db_MaxIndex6(const double s[6])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]>best){best=s[1];pos=1;}
+    if(s[2]>best){best=s[2];pos=2;}
+    if(s[3]>best){best=s[3];pos=3;}
+    if(s[4]>best){best=s[4];pos=4;}
+    if(s[5]>best){best=s[5];pos=5;}
+    return(pos);
+}
+
+inline int db_MaxIndex7(const double s[7])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]>best){best=s[1];pos=1;}
+    if(s[2]>best){best=s[2];pos=2;}
+    if(s[3]>best){best=s[3];pos=3;}
+    if(s[4]>best){best=s[4];pos=4;}
+    if(s[5]>best){best=s[5];pos=5;}
+    if(s[6]>best){best=s[6];pos=6;}
+    return(pos);
+}
+
+inline int db_MinIndex7(const double s[7])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]<best){best=s[1];pos=1;}
+    if(s[2]<best){best=s[2];pos=2;}
+    if(s[3]<best){best=s[3];pos=3;}
+    if(s[4]<best){best=s[4];pos=4;}
+    if(s[5]<best){best=s[5];pos=5;}
+    if(s[6]<best){best=s[6];pos=6;}
+    return(pos);
+}
+
+inline int db_MinIndex9(const double s[9])
+{
+    double best;
+    int pos;
+
+    best=s[0];pos=0;
+    if(s[1]<best){best=s[1];pos=1;}
+    if(s[2]<best){best=s[2];pos=2;}
+    if(s[3]<best){best=s[3];pos=3;}
+    if(s[4]<best){best=s[4];pos=4;}
+    if(s[5]<best){best=s[5];pos=5;}
+    if(s[6]<best){best=s[6];pos=6;}
+    if(s[7]<best){best=s[7];pos=7;}
+    if(s[8]<best){best=s[8];pos=8;}
+    return(pos);
+}
+
+inline int db_MaxAbsIndex3(const double *s)
+{
+    double t,best;
+    int pos;
+
+    best=fabs(s[0]);pos=0;
+    t=fabs(s[1]);if(t>best){best=t;pos=1;}
+    t=fabs(s[2]);if(t>best){pos=2;}
+    return(pos);
+}
+
+inline int db_MaxAbsIndex9(const double *s)
+{
+    double t,best;
+    int pos;
+
+    best=fabs(s[0]);pos=0;
+    t=fabs(s[1]);if(t>best){best=t;pos=1;}
+    t=fabs(s[2]);if(t>best){best=t;pos=2;}
+    t=fabs(s[3]);if(t>best){best=t;pos=3;}
+    t=fabs(s[4]);if(t>best){best=t;pos=4;}
+    t=fabs(s[5]);if(t>best){best=t;pos=5;}
+    t=fabs(s[6]);if(t>best){best=t;pos=6;}
+    t=fabs(s[7]);if(t>best){best=t;pos=7;}
+    t=fabs(s[8]);if(t>best){best=t;pos=8;}
+    return(pos);
+}
+
+
+/*!
+Select ordinal pos (zero based) out of nr_elements in s.
+temp should point to alloced memory of at least nr_elements*2
+Optimized runtimes on 450MHz:
+\code
+  30 with   3 microsecs
+ 100 with  11 microsecs
+ 300 with  30 microsecs
+ 500 with  40 microsecs
+1000 with 100 microsecs
+5000 with 540 microsecs
+\endcode
+so the expected runtime is around
+(nr_elements/10) microseconds
+The total quickselect cost of splitting 500 hypotheses recursively
+is thus around 100 microseconds
+
+Does the same operation as std::nth_element().
+*/
+DB_API double db_LeanQuickSelect(const double *s,long nr_elements,long pos,double *temp);
+
+/*!
+ Median of 3 doubles
+ */
+inline double db_TripleMedian(double a,double b,double c)
+{
+    if(a>b)
+    {
+        if(c>a) return(a);
+        else if(c>b) return(c);
+        else return(b);
+    }
+    else
+    {
+        if(c>b) return(b);
+        else if(c>a) return(c);
+        else return(a);
+    }
+}
+
+/*!
+Align float pointer to nr_bytes by moving forward
+*/
+DB_API float* db_AlignPointer_f(float *p,unsigned long nr_bytes);
+
+/*!
+Align short pointer to nr_bytes by moving forward
+*/
+DB_API short* db_AlignPointer_s(short *p,unsigned long nr_bytes);
+
+#endif /* DB_UTILITIES_INDEXING */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.cpp b/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.cpp
new file mode 100644
index 0000000..8f68b30
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.cpp
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_linalg.cpp,v 1.3 2011/06/17 14:03:31 mbansal Exp $ */
+
+#include "db_utilities_linalg.h"
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+/*Cholesky-factorize symmetric positive definite 6 x 6 matrix A. Upper
+part of A is used from the input. The Cholesky factor is output as
+subdiagonal part in A and diagonal in d, which is 6-dimensional*/
+void db_CholeskyDecomp6x6(double A[36],double d[6])
+{
+    double s,temp;
+
+    /*[50 mult 35 add 6sqrt=85flops 6func]*/
+    /*i=0*/
+    s=A[0];
+    d[0]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[0]);
+    A[6]=A[1]*temp;
+    A[12]=A[2]*temp;
+    A[18]=A[3]*temp;
+    A[24]=A[4]*temp;
+    A[30]=A[5]*temp;
+    /*i=1*/
+    s=A[7]-A[6]*A[6];
+    d[1]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[1]);
+    A[13]=(A[8]-A[6]*A[12])*temp;
+    A[19]=(A[9]-A[6]*A[18])*temp;
+    A[25]=(A[10]-A[6]*A[24])*temp;
+    A[31]=(A[11]-A[6]*A[30])*temp;
+    /*i=2*/
+    s=A[14]-A[12]*A[12]-A[13]*A[13];
+    d[2]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[2]);
+    A[20]=(A[15]-A[12]*A[18]-A[13]*A[19])*temp;
+    A[26]=(A[16]-A[12]*A[24]-A[13]*A[25])*temp;
+    A[32]=(A[17]-A[12]*A[30]-A[13]*A[31])*temp;
+    /*i=3*/
+    s=A[21]-A[18]*A[18]-A[19]*A[19]-A[20]*A[20];
+    d[3]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[3]);
+    A[27]=(A[22]-A[18]*A[24]-A[19]*A[25]-A[20]*A[26])*temp;
+    A[33]=(A[23]-A[18]*A[30]-A[19]*A[31]-A[20]*A[32])*temp;
+    /*i=4*/
+    s=A[28]-A[24]*A[24]-A[25]*A[25]-A[26]*A[26]-A[27]*A[27];
+    d[4]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[4]);
+    A[34]=(A[29]-A[24]*A[30]-A[25]*A[31]-A[26]*A[32]-A[27]*A[33])*temp;
+    /*i=5*/
+    s=A[35]-A[30]*A[30]-A[31]*A[31]-A[32]*A[32]-A[33]*A[33]-A[34]*A[34];
+    d[5]=((s>0.0)?sqrt(s):1.0);
+}
+
+/*Cholesky-factorize symmetric positive definite n x n matrix A.Part
+above diagonal of A is used from the input, diagonal of A is assumed to
+be stored in d. The Cholesky factor is output as
+subdiagonal part in A and diagonal in d, which is n-dimensional*/
+void db_CholeskyDecompSeparateDiagonal(double **A,double *d,int n)
+{
+    int i,j,k;
+    double s;
+    double temp = 0.0;
+
+    for(i=0;i<n;i++) for(j=i;j<n;j++)
+    {
+        if(i==j) s=d[i];
+        else s=A[i][j];
+        for(k=i-1;k>=0;k--) s-=A[i][k]*A[j][k];
+        if(i==j)
+        {
+            d[i]=((s>0.0)?sqrt(s):1.0);
+            temp=db_SafeReciprocal(d[i]);
+        }
+        else A[j][i]=s*temp;
+    }
+}
+
+/*Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of an n x n matrix and the right hand side b. The vector b is unchanged*/
+void db_CholeskyBacksub(double *x,const double * const *A,const double *d,int n,const double *b)
+{
+    int i,k;
+    double s;
+
+    for(i=0;i<n;i++)
+    {
+        for(s=b[i],k=i-1;k>=0;k--) s-=A[i][k]*x[k];
+        x[i]=db_SafeDivision(s,d[i]);
+    }
+    for(i=n-1;i>=0;i--)
+    {
+        for(s=x[i],k=i+1;k<n;k++) s-=A[k][i]*x[k];
+        x[i]=db_SafeDivision(s,d[i]);
+    }
+}
+
+/*Cholesky-factorize symmetric positive definite 3 x 3 matrix A. Part
+above diagonal of A is used from the input, diagonal of A is assumed to
+be stored in d. The Cholesky factor is output as subdiagonal part in A
+and diagonal in d, which is 3-dimensional*/
+void db_CholeskyDecomp3x3SeparateDiagonal(double A[9],double d[3])
+{
+    double s,temp;
+
+    /*i=0*/
+    s=d[0];
+    d[0]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[0]);
+    A[3]=A[1]*temp;
+    A[6]=A[2]*temp;
+    /*i=1*/
+    s=d[1]-A[3]*A[3];
+    d[1]=((s>0.0)?sqrt(s):1.0);
+    temp=db_SafeReciprocal(d[1]);
+    A[7]=(A[5]-A[3]*A[6])*temp;
+    /*i=2*/
+    s=d[2]-A[6]*A[6]-A[7]*A[7];
+    d[2]=((s>0.0)?sqrt(s):1.0);
+}
+
+/*Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of a 3 x 3 matrix and the right hand side b. The vector b is unchanged*/
+void db_CholeskyBacksub3x3(double x[3],const double A[9],const double d[3],const double b[3])
+{
+    /*[42 mult 30 add=72flops]*/
+    x[0]=db_SafeDivision(b[0],d[0]);
+    x[1]=db_SafeDivision((b[1]-A[3]*x[0]),d[1]);
+    x[2]=db_SafeDivision((b[2]-A[6]*x[0]-A[7]*x[1]),d[2]);
+    x[2]=db_SafeDivision(x[2],d[2]);
+    x[1]=db_SafeDivision((x[1]-A[7]*x[2]),d[1]);
+    x[0]=db_SafeDivision((x[0]-A[6]*x[2]-A[3]*x[1]),d[0]);
+}
+
+/*Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of a 6 x 6 matrix and the right hand side b. The vector b is unchanged*/
+void db_CholeskyBacksub6x6(double x[6],const double A[36],const double d[6],const double b[6])
+{
+    /*[42 mult 30 add=72flops]*/
+    x[0]=db_SafeDivision(b[0],d[0]);
+    x[1]=db_SafeDivision((b[1]-A[6]*x[0]),d[1]);
+    x[2]=db_SafeDivision((b[2]-A[12]*x[0]-A[13]*x[1]),d[2]);
+    x[3]=db_SafeDivision((b[3]-A[18]*x[0]-A[19]*x[1]-A[20]*x[2]),d[3]);
+    x[4]=db_SafeDivision((b[4]-A[24]*x[0]-A[25]*x[1]-A[26]*x[2]-A[27]*x[3]),d[4]);
+    x[5]=db_SafeDivision((b[5]-A[30]*x[0]-A[31]*x[1]-A[32]*x[2]-A[33]*x[3]-A[34]*x[4]),d[5]);
+    x[5]=db_SafeDivision(x[5],d[5]);
+    x[4]=db_SafeDivision((x[4]-A[34]*x[5]),d[4]);
+    x[3]=db_SafeDivision((x[3]-A[33]*x[5]-A[27]*x[4]),d[3]);
+    x[2]=db_SafeDivision((x[2]-A[32]*x[5]-A[26]*x[4]-A[20]*x[3]),d[2]);
+    x[1]=db_SafeDivision((x[1]-A[31]*x[5]-A[25]*x[4]-A[19]*x[3]-A[13]*x[2]),d[1]);
+    x[0]=db_SafeDivision((x[0]-A[30]*x[5]-A[24]*x[4]-A[18]*x[3]-A[12]*x[2]-A[6]*x[1]),d[0]);
+}
+
+
+void db_Orthogonalize6x7(double A[42],int orthonormalize)
+{
+    int i;
+    double ss[6];
+
+    /*Compute square sums of rows*/
+    ss[0]=db_SquareSum7(A);
+    ss[1]=db_SquareSum7(A+7);
+    ss[2]=db_SquareSum7(A+14);
+    ss[3]=db_SquareSum7(A+21);
+    ss[4]=db_SquareSum7(A+28);
+    ss[5]=db_SquareSum7(A+35);
+
+    ss[1]-=db_OrthogonalizePair7(A+7 ,A,ss[0]);
+    ss[2]-=db_OrthogonalizePair7(A+14,A,ss[0]);
+    ss[3]-=db_OrthogonalizePair7(A+21,A,ss[0]);
+    ss[4]-=db_OrthogonalizePair7(A+28,A,ss[0]);
+    ss[5]-=db_OrthogonalizePair7(A+35,A,ss[0]);
+
+    /*Pivot on largest ss (could also be done on ss/(original_ss))*/
+    i=db_MaxIndex5(ss+1);
+    db_OrthogonalizationSwap7(A+7,i,ss+1);
+
+    ss[2]-=db_OrthogonalizePair7(A+14,A+7,ss[1]);
+    ss[3]-=db_OrthogonalizePair7(A+21,A+7,ss[1]);
+    ss[4]-=db_OrthogonalizePair7(A+28,A+7,ss[1]);
+    ss[5]-=db_OrthogonalizePair7(A+35,A+7,ss[1]);
+
+    i=db_MaxIndex4(ss+2);
+    db_OrthogonalizationSwap7(A+14,i,ss+2);
+
+    ss[3]-=db_OrthogonalizePair7(A+21,A+14,ss[2]);
+    ss[4]-=db_OrthogonalizePair7(A+28,A+14,ss[2]);
+    ss[5]-=db_OrthogonalizePair7(A+35,A+14,ss[2]);
+
+    i=db_MaxIndex3(ss+3);
+    db_OrthogonalizationSwap7(A+21,i,ss+3);
+
+    ss[4]-=db_OrthogonalizePair7(A+28,A+21,ss[3]);
+    ss[5]-=db_OrthogonalizePair7(A+35,A+21,ss[3]);
+
+    i=db_MaxIndex2(ss+4);
+    db_OrthogonalizationSwap7(A+28,i,ss+4);
+
+    ss[5]-=db_OrthogonalizePair7(A+35,A+28,ss[4]);
+
+    if(orthonormalize)
+    {
+        db_MultiplyScalar7(A   ,db_SafeSqrtReciprocal(ss[0]));
+        db_MultiplyScalar7(A+7 ,db_SafeSqrtReciprocal(ss[1]));
+        db_MultiplyScalar7(A+14,db_SafeSqrtReciprocal(ss[2]));
+        db_MultiplyScalar7(A+21,db_SafeSqrtReciprocal(ss[3]));
+        db_MultiplyScalar7(A+28,db_SafeSqrtReciprocal(ss[4]));
+        db_MultiplyScalar7(A+35,db_SafeSqrtReciprocal(ss[5]));
+    }
+}
+
+void db_Orthogonalize8x9(double A[72],int orthonormalize)
+{
+    int i;
+    double ss[8];
+
+    /*Compute square sums of rows*/
+    ss[0]=db_SquareSum9(A);
+    ss[1]=db_SquareSum9(A+9);
+    ss[2]=db_SquareSum9(A+18);
+    ss[3]=db_SquareSum9(A+27);
+    ss[4]=db_SquareSum9(A+36);
+    ss[5]=db_SquareSum9(A+45);
+    ss[6]=db_SquareSum9(A+54);
+    ss[7]=db_SquareSum9(A+63);
+
+    ss[1]-=db_OrthogonalizePair9(A+9 ,A,ss[0]);
+    ss[2]-=db_OrthogonalizePair9(A+18,A,ss[0]);
+    ss[3]-=db_OrthogonalizePair9(A+27,A,ss[0]);
+    ss[4]-=db_OrthogonalizePair9(A+36,A,ss[0]);
+    ss[5]-=db_OrthogonalizePair9(A+45,A,ss[0]);
+    ss[6]-=db_OrthogonalizePair9(A+54,A,ss[0]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A,ss[0]);
+
+    /*Pivot on largest ss (could also be done on ss/(original_ss))*/
+    i=db_MaxIndex7(ss+1);
+    db_OrthogonalizationSwap9(A+9,i,ss+1);
+
+    ss[2]-=db_OrthogonalizePair9(A+18,A+9,ss[1]);
+    ss[3]-=db_OrthogonalizePair9(A+27,A+9,ss[1]);
+    ss[4]-=db_OrthogonalizePair9(A+36,A+9,ss[1]);
+    ss[5]-=db_OrthogonalizePair9(A+45,A+9,ss[1]);
+    ss[6]-=db_OrthogonalizePair9(A+54,A+9,ss[1]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A+9,ss[1]);
+
+    i=db_MaxIndex6(ss+2);
+    db_OrthogonalizationSwap9(A+18,i,ss+2);
+
+    ss[3]-=db_OrthogonalizePair9(A+27,A+18,ss[2]);
+    ss[4]-=db_OrthogonalizePair9(A+36,A+18,ss[2]);
+    ss[5]-=db_OrthogonalizePair9(A+45,A+18,ss[2]);
+    ss[6]-=db_OrthogonalizePair9(A+54,A+18,ss[2]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A+18,ss[2]);
+
+    i=db_MaxIndex5(ss+3);
+    db_OrthogonalizationSwap9(A+27,i,ss+3);
+
+    ss[4]-=db_OrthogonalizePair9(A+36,A+27,ss[3]);
+    ss[5]-=db_OrthogonalizePair9(A+45,A+27,ss[3]);
+    ss[6]-=db_OrthogonalizePair9(A+54,A+27,ss[3]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A+27,ss[3]);
+
+    i=db_MaxIndex4(ss+4);
+    db_OrthogonalizationSwap9(A+36,i,ss+4);
+
+    ss[5]-=db_OrthogonalizePair9(A+45,A+36,ss[4]);
+    ss[6]-=db_OrthogonalizePair9(A+54,A+36,ss[4]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A+36,ss[4]);
+
+    i=db_MaxIndex3(ss+5);
+    db_OrthogonalizationSwap9(A+45,i,ss+5);
+
+    ss[6]-=db_OrthogonalizePair9(A+54,A+45,ss[5]);
+    ss[7]-=db_OrthogonalizePair9(A+63,A+45,ss[5]);
+
+    i=db_MaxIndex2(ss+6);
+    db_OrthogonalizationSwap9(A+54,i,ss+6);
+
+    ss[7]-=db_OrthogonalizePair9(A+63,A+54,ss[6]);
+
+    if(orthonormalize)
+    {
+        db_MultiplyScalar9(A   ,db_SafeSqrtReciprocal(ss[0]));
+        db_MultiplyScalar9(A+9 ,db_SafeSqrtReciprocal(ss[1]));
+        db_MultiplyScalar9(A+18,db_SafeSqrtReciprocal(ss[2]));
+        db_MultiplyScalar9(A+27,db_SafeSqrtReciprocal(ss[3]));
+        db_MultiplyScalar9(A+36,db_SafeSqrtReciprocal(ss[4]));
+        db_MultiplyScalar9(A+45,db_SafeSqrtReciprocal(ss[5]));
+        db_MultiplyScalar9(A+54,db_SafeSqrtReciprocal(ss[6]));
+        db_MultiplyScalar9(A+63,db_SafeSqrtReciprocal(ss[7]));
+    }
+}
+
+void db_NullVectorOrthonormal6x7(double x[7],const double A[42])
+{
+    int i;
+    double omss[7];
+    const double *B;
+
+    /*Pivot by choosing row of the identity matrix
+    (the one corresponding to column of A with smallest square sum)*/
+    omss[0]=db_SquareSum6Stride7(A);
+    omss[1]=db_SquareSum6Stride7(A+1);
+    omss[2]=db_SquareSum6Stride7(A+2);
+    omss[3]=db_SquareSum6Stride7(A+3);
+    omss[4]=db_SquareSum6Stride7(A+4);
+    omss[5]=db_SquareSum6Stride7(A+5);
+    omss[6]=db_SquareSum6Stride7(A+6);
+    i=db_MinIndex7(omss);
+    /*orthogonalize that row against all previous rows
+    and normalize it*/
+    B=A+i;
+    db_MultiplyScalarCopy7(x,A,-B[0]);
+    db_RowOperation7(x,A+7 ,B[7]);
+    db_RowOperation7(x,A+14,B[14]);
+    db_RowOperation7(x,A+21,B[21]);
+    db_RowOperation7(x,A+28,B[28]);
+    db_RowOperation7(x,A+35,B[35]);
+    x[i]+=1.0;
+    db_MultiplyScalar7(x,db_SafeSqrtReciprocal(1.0-omss[i]));
+}
+
+void db_NullVectorOrthonormal8x9(double x[9],const double A[72])
+{
+    int i;
+    double omss[9];
+    const double *B;
+
+    /*Pivot by choosing row of the identity matrix
+    (the one corresponding to column of A with smallest square sum)*/
+    omss[0]=db_SquareSum8Stride9(A);
+    omss[1]=db_SquareSum8Stride9(A+1);
+    omss[2]=db_SquareSum8Stride9(A+2);
+    omss[3]=db_SquareSum8Stride9(A+3);
+    omss[4]=db_SquareSum8Stride9(A+4);
+    omss[5]=db_SquareSum8Stride9(A+5);
+    omss[6]=db_SquareSum8Stride9(A+6);
+    omss[7]=db_SquareSum8Stride9(A+7);
+    omss[8]=db_SquareSum8Stride9(A+8);
+    i=db_MinIndex9(omss);
+    /*orthogonalize that row against all previous rows
+    and normalize it*/
+    B=A+i;
+    db_MultiplyScalarCopy9(x,A,-B[0]);
+    db_RowOperation9(x,A+9 ,B[9]);
+    db_RowOperation9(x,A+18,B[18]);
+    db_RowOperation9(x,A+27,B[27]);
+    db_RowOperation9(x,A+36,B[36]);
+    db_RowOperation9(x,A+45,B[45]);
+    db_RowOperation9(x,A+54,B[54]);
+    db_RowOperation9(x,A+63,B[63]);
+    x[i]+=1.0;
+    db_MultiplyScalar9(x,db_SafeSqrtReciprocal(1.0-omss[i]));
+}
+
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.h
new file mode 100644
index 0000000..1f63d4e
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_linalg.h
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_linalg.h,v 1.5 2011/06/17 14:03:31 mbansal Exp $ */
+
+#ifndef DB_UTILITIES_LINALG
+#define DB_UTILITIES_LINALG
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMLinAlg (LM) Linear Algebra Utilities (QR factorization, orthogonal basis, etc.)
+ */
+
+/*!
+ \ingroup LMBasicUtilities
+ */
+inline void db_MultiplyScalar6(double A[6],double mult)
+{
+    (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult;
+    (*A++) *= mult;
+}
+/*!
+ \ingroup LMBasicUtilities
+ */
+inline void db_MultiplyScalar7(double A[7],double mult)
+{
+    (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult;
+    (*A++) *= mult; (*A++) *= mult;
+}
+/*!
+ \ingroup LMBasicUtilities
+ */
+inline void db_MultiplyScalar9(double A[9],double mult)
+{
+    (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult;
+    (*A++) *= mult; (*A++) *= mult; (*A++) *= mult; (*A++) *= mult;
+}
+
+/*!
+ \ingroup LMBasicUtilities
+ */
+inline double db_SquareSum6Stride7(const double *x)
+{
+    return(db_sqr(x[0])+db_sqr(x[7])+db_sqr(x[14])+
+        db_sqr(x[21])+db_sqr(x[28])+db_sqr(x[35]));
+}
+
+/*!
+ \ingroup LMBasicUtilities
+ */
+inline double db_SquareSum8Stride9(const double *x)
+{
+    return(db_sqr(x[0])+db_sqr(x[9])+db_sqr(x[18])+
+        db_sqr(x[27])+db_sqr(x[36])+db_sqr(x[45])+
+        db_sqr(x[54])+db_sqr(x[63]));
+}
+
+/*!
+ \ingroup LMLinAlg
+ Cholesky-factorize symmetric positive definite 6 x 6 matrix A. Upper
+part of A is used from the input. The Cholesky factor is output as
+subdiagonal part in A and diagonal in d, which is 6-dimensional
+1.9 microseconds on 450MHz*/
+DB_API void db_CholeskyDecomp6x6(double A[36],double d[6]);
+
+/*!
+ \ingroup LMLinAlg
+ Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of a 6 x 6 matrix and the right hand side b. The vector b is unchanged
+1.3 microseconds on 450MHz*/
+DB_API void db_CholeskyBacksub6x6(double x[6],const double A[36],const double d[6],const double b[6]);
+
+/*!
+ \ingroup LMLinAlg
+ Cholesky-factorize symmetric positive definite n x n matrix A.Part
+above diagonal of A is used from the input, diagonal of A is assumed to
+be stored in d. The Cholesky factor is output as
+subdiagonal part in A and diagonal in d, which is n-dimensional*/
+DB_API void db_CholeskyDecompSeparateDiagonal(double **A,double *d,int n);
+
+/*!
+ \ingroup LMLinAlg
+ Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of an n x n matrix and the right hand side b. The vector b is unchanged*/
+DB_API void db_CholeskyBacksub(double *x,const double * const *A,const double *d,int n,const double *b);
+
+/*!
+ \ingroup LMLinAlg
+ Cholesky-factorize symmetric positive definite 3 x 3 matrix A. Part
+above diagonal of A is used from the input, diagonal of A is assumed to
+be stored in d. The Cholesky factor is output as subdiagonal part in A
+and diagonal in d, which is 3-dimensional*/
+DB_API void db_CholeskyDecomp3x3SeparateDiagonal(double A[9],double d[3]);
+
+/*!
+ \ingroup LMLinAlg
+ Backsubstitute L%transpose(L)*x=b for x given the Cholesky decomposition
+of a 3 x 3 matrix and the right hand side b. The vector b is unchanged*/
+DB_API void db_CholeskyBacksub3x3(double x[3],const double A[9],const double d[3],const double b[3]);
+
+/*!
+ \ingroup LMLinAlg
+ perform A-=B*mult*/
+inline void db_RowOperation3(double A[3],const double B[3],double mult)
+{
+    *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++);
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_RowOperation7(double A[7],const double B[7],double mult)
+{
+    *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++);
+    *A++ -= mult*(*B++); *A++ -= mult*(*B++);
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_RowOperation9(double A[9],const double B[9],double mult)
+{
+    *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++);
+    *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++); *A++ -= mult*(*B++);
+}
+
+/*!
+ \ingroup LMBasicUtilities
+ Swap values of A[7] and B[7]
+ */
+inline void db_Swap7(double A[7],double B[7])
+{
+    double temp;
+    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;
+    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;
+    temp= *A; *A++ = *B; *B++ =temp;
+}
+
+/*!
+ \ingroup LMBasicUtilities
+ Swap values of A[9] and B[9]
+ */
+inline void db_Swap9(double A[9],double B[9])
+{
+    double temp;
+    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;
+    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;
+    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;    temp= *A; *A++ = *B; *B++ =temp;
+}
+
+
+/*!
+ \ingroup LMLinAlg
+ */
+DB_API void db_Orthogonalize6x7(double A[42],int orthonormalize=0);
+
+/*!
+ \ingroup LMLinAlg
+ */
+DB_API void db_Orthogonalize8x9(double A[72],int orthonormalize=0);
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline double db_OrthogonalizePair7(double *x,const double *v,double ssv)
+{
+    double m,sp,sp_m;
+
+    m=db_SafeReciprocal(ssv);
+    sp=db_ScalarProduct7(x,v);
+    sp_m=sp*m;
+    db_RowOperation7(x,v,sp_m);
+    return(sp*sp_m);
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline double db_OrthogonalizePair9(double *x,const double *v,double ssv)
+{
+    double m,sp,sp_m;
+
+    m=db_SafeReciprocal(ssv);
+    sp=db_ScalarProduct9(x,v);
+    sp_m=sp*m;
+    db_RowOperation9(x,v,sp_m);
+    return(sp*sp_m);
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_OrthogonalizationSwap7(double *A,int i,double *ss)
+{
+    double temp;
+
+    db_Swap7(A,A+7*i);
+    temp=ss[0]; ss[0]=ss[i]; ss[i]=temp;
+}
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_OrthogonalizationSwap9(double *A,int i,double *ss)
+{
+    double temp;
+
+    db_Swap9(A,A+9*i);
+    temp=ss[0]; ss[0]=ss[i]; ss[i]=temp;
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+DB_API void db_NullVectorOrthonormal6x7(double x[7],const double A[42]);
+/*!
+ \ingroup LMLinAlg
+ */
+DB_API void db_NullVectorOrthonormal8x9(double x[9],const double A[72]);
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_NullVector6x7Destructive(double x[7],double A[42])
+{
+    db_Orthogonalize6x7(A,1);
+    db_NullVectorOrthonormal6x7(x,A);
+}
+
+/*!
+ \ingroup LMLinAlg
+ */
+inline void db_NullVector8x9Destructive(double x[9],double A[72])
+{
+    db_Orthogonalize8x9(A,1);
+    db_NullVectorOrthonormal8x9(x,A);
+}
+
+inline int db_ScalarProduct512_s(const short *f,const short *g)
+{
+#ifndef DB_USE_MMX
+    int back;
+    back=0;
+    for(int i=1; i<=512; i++)
+        back+=(*f++)*(*g++);
+
+    return(back);
+#endif
+}
+
+
+inline int db_ScalarProduct32_s(const short *f,const short *g)
+{
+#ifndef DB_USE_MMX
+    int back;
+    back=0;
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    return(back);
+#endif
+}
+
+/*!
+ \ingroup LMLinAlg
+ Scalar product of 128-vectors (short)
+  Compile-time control: MMX, SSE2 or standard C
+ */
+inline int db_ScalarProduct128_s(const short *f,const short *g)
+{
+#ifndef DB_USE_MMX
+    int back;
+    back=0;
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    return(back);
+#else
+#ifdef DB_USE_SSE2
+    int back;
+
+    _asm
+    {
+        mov eax,f
+        mov ecx,g
+        /*First iteration************************************/
+        movdqa     xmm0,[eax]
+         pxor    xmm7,xmm7      /*Set xmm7 to zero*/
+        pmaddwd  xmm0,[ecx]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm2,[eax+16]
+         paddd   xmm7,xmm0
+        pmaddwd  xmm2,[ecx+16]
+         /*Stall*/
+        movdqa     xmm1,[eax+32]
+         paddd   xmm7,xmm2
+        pmaddwd  xmm1,[ecx+32]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm0,[eax+48]
+         paddd   xmm7,xmm1
+        pmaddwd  xmm0,[ecx+48]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm2,[eax+64]
+         paddd   xmm7,xmm0
+        pmaddwd  xmm2,[ecx+64]
+         /*Stall*/
+        movdqa     xmm1,[eax+80]
+         paddd   xmm7,xmm2
+        pmaddwd  xmm1,[ecx+80]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm0,[eax+96]
+         paddd   xmm7,xmm1
+        pmaddwd  xmm0,[ecx+96]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm2,[eax+112]
+         paddd   xmm7,xmm0
+        pmaddwd  xmm2,[ecx+112]
+         /*Stall*/
+        movdqa     xmm1,[eax+128]
+         paddd   xmm7,xmm2
+        pmaddwd  xmm1,[ecx+128]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm0,[eax+144]
+         paddd   xmm7,xmm1
+        pmaddwd  xmm0,[ecx+144]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm2,[eax+160]
+         paddd   xmm7,xmm0
+        pmaddwd  xmm2,[ecx+160]
+         /*Stall*/
+        movdqa     xmm1,[eax+176]
+         paddd   xmm7,xmm2
+        pmaddwd  xmm1,[ecx+176]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm0,[eax+192]
+         paddd   xmm7,xmm1
+        pmaddwd  xmm0,[ecx+192]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm2,[eax+208]
+         paddd   xmm7,xmm0
+        pmaddwd  xmm2,[ecx+208]
+         /*Stall*/
+        movdqa     xmm1,[eax+224]
+         paddd   xmm7,xmm2
+        pmaddwd  xmm1,[ecx+224]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movdqa     xmm0,[eax+240]
+         paddd   xmm7,xmm1
+        pmaddwd  xmm0,[ecx+240]
+         /*Stall*/
+        /*Rest iteration************************************/
+        paddd    xmm7,xmm0
+
+        /* add up the sum squares */
+        movhlps     xmm0,xmm7   /* high half to low half */
+        paddd       xmm7,xmm0   /* add high to low */
+        pshuflw     xmm0,xmm7, 0xE /* reshuffle */
+        paddd       xmm7,xmm0   /* add remaining */
+        movd        back,xmm7
+
+        emms
+    }
+
+    return(back);
+#else
+    int back;
+
+    _asm
+    {
+        mov eax,f
+        mov ecx,g
+        /*First iteration************************************/
+        movq     mm0,[eax]
+         pxor    mm7,mm7      /*Set mm7 to zero*/
+        pmaddwd  mm0,[ecx]
+         /*Stall*/
+        movq     mm1,[eax+8]
+         /*Stall*/
+        pmaddwd  mm1,[ecx+8]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+16]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+16]
+         /*Stall*/
+        movq     mm0,[eax+24]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+24]
+         /*Stall*/
+        movq     mm1,[eax+32]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+32]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+40]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+40]
+         /*Stall*/
+        movq     mm0,[eax+48]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+48]
+         /*Stall*/
+        movq     mm1,[eax+56]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+56]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+64]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+64]
+         /*Stall*/
+        movq     mm0,[eax+72]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+72]
+         /*Stall*/
+        movq     mm1,[eax+80]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+80]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+88]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+88]
+         /*Stall*/
+        movq     mm0,[eax+96]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+96]
+         /*Stall*/
+        movq     mm1,[eax+104]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+104]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+112]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+112]
+         /*Stall*/
+        movq     mm0,[eax+120]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+120]
+         /*Stall*/
+        movq     mm1,[eax+128]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+128]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+136]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+136]
+         /*Stall*/
+        movq     mm0,[eax+144]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+144]
+         /*Stall*/
+        movq     mm1,[eax+152]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+152]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+160]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+160]
+         /*Stall*/
+        movq     mm0,[eax+168]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+168]
+         /*Stall*/
+        movq     mm1,[eax+176]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+176]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+184]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+184]
+         /*Stall*/
+        movq     mm0,[eax+192]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+192]
+         /*Stall*/
+        movq     mm1,[eax+200]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+200]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+208]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+208]
+         /*Stall*/
+        movq     mm0,[eax+216]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+216]
+         /*Stall*/
+        movq     mm1,[eax+224]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+224]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movq     mm2,[eax+232]
+         paddd   mm7,mm0
+        pmaddwd  mm2,[ecx+232]
+         /*Stall*/
+        movq     mm0,[eax+240]
+         paddd   mm7,mm1
+        pmaddwd  mm0,[ecx+240]
+         /*Stall*/
+        movq     mm1,[eax+248]
+         paddd   mm7,mm2
+        pmaddwd  mm1,[ecx+248]
+         /*Stall*/
+        /*Rest iteration************************************/
+        paddd    mm7,mm0
+         /*Stall*/
+        /*Stall*/
+         /*Stall*/
+        paddd    mm7,mm1
+         /*Stall*/
+        movq     mm0,mm7
+         psrlq   mm7,32
+        paddd    mm0,mm7
+         /*Stall*/
+        /*Stall*/
+         /*Stall*/
+        movd     back,mm0
+        emms
+    }
+
+    return(back);
+#endif
+#endif /*DB_USE_MMX*/
+}
+
+/*!
+ \ingroup LMLinAlg
+ Scalar product of 16 byte aligned 128-vectors (float).
+  Compile-time control: SIMD (SSE) or standard C.
+ */
+inline float db_ScalarProduct128Aligned16_f(const float *f,const float *g)
+{
+#ifndef DB_USE_SIMD
+    float back;
+    back=0.0;
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+    back+=(*f++)*(*g++); back+=(*f++)*(*g++); back+=(*f++)*(*g++);
+
+    return(back);
+#else
+    float back;
+
+    _asm
+    {
+        mov eax,f
+        mov ecx,g
+        /*First iteration************************************/
+        movaps     xmm0,[eax]
+         xorps      xmm7,xmm7       /*Set mm7 to zero*/
+        mulps      xmm0,[ecx]
+         /*Stall*/
+        movaps     xmm1,[eax+16]
+         /*Stall*/
+        mulps      xmm1,[ecx+16]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+32]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+32]
+         /*Stall*/
+        movaps     xmm0,[eax+48]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+48]
+         /*Stall*/
+        movaps     xmm1,[eax+64]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+64]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+80]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+80]
+         /*Stall*/
+        movaps     xmm0,[eax+96]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+96]
+         /*Stall*/
+        movaps     xmm1,[eax+112]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+112]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+128]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+128]
+         /*Stall*/
+        movaps     xmm0,[eax+144]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+144]
+         /*Stall*/
+        movaps     xmm1,[eax+160]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+160]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+176]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+176]
+         /*Stall*/
+        movaps     xmm0,[eax+192]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+192]
+         /*Stall*/
+        movaps     xmm1,[eax+208]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+208]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+224]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+224]
+         /*Stall*/
+        movaps     xmm0,[eax+240]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+240]
+         /*Stall*/
+        movaps     xmm1,[eax+256]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+256]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+272]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+272]
+         /*Stall*/
+        movaps     xmm0,[eax+288]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+288]
+         /*Stall*/
+        movaps     xmm1,[eax+304]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+304]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+320]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+320]
+         /*Stall*/
+        movaps     xmm0,[eax+336]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+336]
+         /*Stall*/
+        movaps     xmm1,[eax+352]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+352]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+368]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+368]
+         /*Stall*/
+        movaps     xmm0,[eax+384]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+384]
+         /*Stall*/
+        movaps     xmm1,[eax+400]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+400]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+416]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+416]
+         /*Stall*/
+        movaps     xmm0,[eax+432]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+432]
+         /*Stall*/
+        movaps     xmm1,[eax+448]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+448]
+         /*Stall*/
+        /*Standard iteration************************************/
+        movaps     xmm2,[eax+464]
+         addps      xmm7,xmm0
+        mulps      xmm2,[ecx+464]
+         /*Stall*/
+        movaps     xmm0,[eax+480]
+         addps      xmm7,xmm1
+        mulps      xmm0,[ecx+480]
+         /*Stall*/
+        movaps     xmm1,[eax+496]
+         addps      xmm7,xmm2
+        mulps      xmm1,[ecx+496]
+         /*Stall*/
+        /*Rest iteration************************************/
+        addps      xmm7,xmm0
+         /*Stall*/
+        addps      xmm7,xmm1
+         /*Stall*/
+        movaps xmm6,xmm7
+         /*Stall*/
+        shufps xmm6,xmm6,4Eh
+         /*Stall*/
+        addps  xmm7,xmm6
+         /*Stall*/
+        movaps xmm6,xmm7
+         /*Stall*/
+        shufps xmm6,xmm6,11h
+         /*Stall*/
+        addps  xmm7,xmm6
+         /*Stall*/
+        movss  back,xmm7
+    }
+
+    return(back);
+#endif /*DB_USE_SIMD*/
+}
+
+#endif /* DB_UTILITIES_LINALG */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.cpp b/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.cpp
new file mode 100644
index 0000000..013ac72
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.cpp
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_poly.cpp,v 1.2 2010/09/03 12:00:10 bsouthall Exp $ */
+
+#include "db_utilities_poly.h"
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+
+void db_SolveCubic(double *roots,int *nr_roots,double a,double b,double c,double d)
+{
+    double bp,bp2,cp,dp,q,r,srq;
+    double r2_min_q3,theta,bp_through3,theta_through3;
+    double cos_theta_through3,sin_theta_through3,min2_cos_theta_plu,min2_cos_theta_min;
+    double si_r_srq,A;
+
+    /*For nondegenerate cubics with three roots
+    [24 mult 9 add 2sqrt 1acos 1cos=33flops 4func]
+    For nondegenerate cubics with one root
+    [16 mult 6 add 1sqrt 1qbrt=24flops 3func]*/
+
+    if(a==0.0) db_SolveQuadratic(roots,nr_roots,b,c,d);
+    else
+    {
+        bp=b/a;
+        bp2=bp*bp;
+        cp=c/a;
+        dp=d/a;
+
+        q=(bp2-3.0*cp)/9.0;
+        r=(2.0*bp2*bp-9.0*bp*cp+27.0*dp)/54.0;
+        r2_min_q3=r*r-q*q*q;
+        if(r2_min_q3<0.0)
+        {
+            *nr_roots=3;
+            /*q has to be > 0*/
+            srq=sqrt(q);
+            theta=acos(db_maxd(-1.0,db_mind(1.0,r/(q*srq))));
+            bp_through3=bp/3.0;
+            theta_through3=theta/3.0;
+            cos_theta_through3=cos(theta_through3);
+            sin_theta_through3=sqrt(db_maxd(0.0,1.0-cos_theta_through3*cos_theta_through3));
+
+            /*cos(theta_through3+2*pi/3)=cos_theta_through3*cos(2*pi/3)-sin_theta_through3*sin(2*pi/3)
+            = -0.5*cos_theta_through3-sqrt(3)/2.0*sin_theta_through3
+            = -0.5*(cos_theta_through3+sqrt(3)*sin_theta_through3)*/
+            min2_cos_theta_plu=cos_theta_through3+DB_SQRT3*sin_theta_through3;
+            min2_cos_theta_min=cos_theta_through3-DB_SQRT3*sin_theta_through3;
+
+            roots[0]= -2.0*srq*cos_theta_through3-bp_through3;
+            roots[1]=srq*min2_cos_theta_plu-bp_through3;
+            roots[2]=srq*min2_cos_theta_min-bp_through3;
+        }
+        else if(r2_min_q3>0.0)
+        {
+            *nr_roots=1;
+            A= -db_sign(r)*db_CubRoot(db_absd(r)+sqrt(r2_min_q3));
+            bp_through3=bp/3.0;
+            if(A!=0.0) roots[0]=A+q/A-bp_through3;
+            else roots[0]= -bp_through3;
+        }
+        else
+        {
+            *nr_roots=2;
+            bp_through3=bp/3.0;
+            /*q has to be >= 0*/
+            si_r_srq=db_sign(r)*sqrt(q);
+            /*Single root*/
+            roots[0]= -2.0*si_r_srq-bp_through3;
+            /*Double root*/
+            roots[1]=si_r_srq-bp_through3;
+        }
+    }
+}
+
+void db_SolveQuartic(double *roots,int *nr_roots,double a,double b,double c,double d,double e)
+{
+    /*Normalized coefficients*/
+    double c0,c1,c2,c3;
+    /*Temporary coefficients*/
+    double c3through2,c3through4,c3c3through4_min_c2,min4_c0;
+    double lz,ms,ns,mn,m,n,lz_through2;
+    /*Cubic polynomial roots, nr of roots and coefficients*/
+    double c_roots[3];
+    int nr_c_roots;
+    double k0,k1;
+    /*nr additional roots from second quadratic*/
+    int addroots;
+
+    /*For nondegenerate quartics
+    [16mult 11add 2sqrt 1cubic 2quadratic=74flops 8funcs]*/
+
+    if(a==0.0) db_SolveCubic(roots,nr_roots,b,c,d,e);
+    else if(e==0.0)
+    {
+        db_SolveCubic(roots,nr_roots,a,b,c,d);
+        roots[*nr_roots]=0.0;
+        *nr_roots+=1;
+    }
+    else
+    {
+        /*Compute normalized coefficients*/
+        c3=b/a;
+        c2=c/a;
+        c1=d/a;
+        c0=e/a;
+        /*Compute temporary coefficients*/
+        c3through2=c3/2.0;
+        c3through4=c3/4.0;
+        c3c3through4_min_c2=c3*c3through4-c2;
+        min4_c0= -4.0*c0;
+        /*Compute coefficients of cubic*/
+        k0=min4_c0*c3c3through4_min_c2-c1*c1;
+        k1=c1*c3+min4_c0;
+        /*k2= -c2*/
+        /*k3=1.0*/
+
+        /*Solve it for roots*/
+        db_SolveCubic(c_roots,&nr_c_roots,1.0,-c2,k1,k0);
+
+        if(nr_c_roots>0)
+        {
+            lz=c_roots[0];
+            lz_through2=lz/2.0;
+            ms=lz+c3c3through4_min_c2;
+            ns=lz_through2*lz_through2-c0;
+            mn=lz*c3through4-c1/2.0;
+
+            if((ms>=0.0)&&(ns>=0.0))
+            {
+                m=sqrt(ms);
+                n=sqrt(ns)*db_sign(mn);
+
+                db_SolveQuadratic(roots,nr_roots,
+                    1.0,c3through2+m,lz_through2+n);
+
+                db_SolveQuadratic(&roots[*nr_roots],&addroots,
+                    1.0,c3through2-m,lz_through2-n);
+
+                *nr_roots+=addroots;
+            }
+            else *nr_roots=0;
+        }
+        else *nr_roots=0;
+    }
+}
+
+void db_SolveQuarticForced(double *roots,int *nr_roots,double a,double b,double c,double d,double e)
+{
+    /*Normalized coefficients*/
+    double c0,c1,c2,c3;
+    /*Temporary coefficients*/
+    double c3through2,c3through4,c3c3through4_min_c2,min4_c0;
+    double lz,ms,ns,mn,m,n,lz_through2;
+    /*Cubic polynomial roots, nr of roots and coefficients*/
+    double c_roots[3];
+    int nr_c_roots;
+    double k0,k1;
+    /*nr additional roots from second quadratic*/
+    int addroots;
+
+    /*For nondegenerate quartics
+    [16mult 11add 2sqrt 1cubic 2quadratic=74flops 8funcs]*/
+
+    if(a==0.0) db_SolveCubic(roots,nr_roots,b,c,d,e);
+    else if(e==0.0)
+    {
+        db_SolveCubic(roots,nr_roots,a,b,c,d);
+        roots[*nr_roots]=0.0;
+        *nr_roots+=1;
+    }
+    else
+    {
+        /*Compute normalized coefficients*/
+        c3=b/a;
+        c2=c/a;
+        c1=d/a;
+        c0=e/a;
+        /*Compute temporary coefficients*/
+        c3through2=c3/2.0;
+        c3through4=c3/4.0;
+        c3c3through4_min_c2=c3*c3through4-c2;
+        min4_c0= -4.0*c0;
+        /*Compute coefficients of cubic*/
+        k0=min4_c0*c3c3through4_min_c2-c1*c1;
+        k1=c1*c3+min4_c0;
+        /*k2= -c2*/
+        /*k3=1.0*/
+
+        /*Solve it for roots*/
+        db_SolveCubic(c_roots,&nr_c_roots,1.0,-c2,k1,k0);
+
+        if(nr_c_roots>0)
+        {
+            lz=c_roots[0];
+            lz_through2=lz/2.0;
+            ms=lz+c3c3through4_min_c2;
+            ns=lz_through2*lz_through2-c0;
+            mn=lz*c3through4-c1/2.0;
+
+            if(ms<0.0) ms=0.0;
+            if(ns<0.0) ns=0.0;
+
+            m=sqrt(ms);
+            n=sqrt(ns)*db_sign(mn);
+
+            db_SolveQuadratic(roots,nr_roots,
+                1.0,c3through2+m,lz_through2+n);
+
+            db_SolveQuadratic(&roots[*nr_roots],&addroots,
+                1.0,c3through2-m,lz_through2-n);
+
+            *nr_roots+=addroots;
+        }
+        else *nr_roots=0;
+    }
+}
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.h
new file mode 100644
index 0000000..1f87890
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_poly.h
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_poly.h,v 1.2 2010/09/03 12:00:11 bsouthall Exp $ */
+
+#ifndef DB_UTILITIES_POLY
+#define DB_UTILITIES_POLY
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMPolynomial (LM) Polynomial utilities (solvers, arithmetic, evaluation, etc.)
+ */
+/*\{*/
+
+/*!
+In debug mode closed form quadratic solving takes on the order of 15 microseconds
+while eig of the companion matrix takes about 1.1 milliseconds
+Speed-optimized code in release mode solves a quadratic in 0.3 microseconds on 450MHz
+*/
+inline void db_SolveQuadratic(double *roots,int *nr_roots,double a,double b,double c)
+{
+    double rs,srs,q;
+
+    /*For non-degenerate quadratics
+    [5 mult 2 add 1 sqrt=7flops 1func]*/
+    if(a==0.0)
+    {
+        if(b==0.0) *nr_roots=0;
+        else
+        {
+            roots[0]= -c/b;
+            *nr_roots=1;
+        }
+    }
+    else
+    {
+        rs=b*b-4.0*a*c;
+        if(rs>=0.0)
+        {
+            *nr_roots=2;
+            srs=sqrt(rs);
+            q= -0.5*(b+db_sign(b)*srs);
+            roots[0]=q/a;
+            /*If b is zero db_sign(b) returns 1,
+            so q is only zero when b=0 and c=0*/
+            if(q==0.0) *nr_roots=1;
+            else roots[1]=c/q;
+        }
+        else *nr_roots=0;
+    }
+}
+
+/*!
+In debug mode closed form cubic solving takes on the order of 45 microseconds
+while eig of the companion matrix takes about 1.3 milliseconds
+Speed-optimized code in release mode solves a cubic in 1.5 microseconds on 450MHz
+For a non-degenerate cubic with two roots, the first root is the single root and
+the second root is the double root
+*/
+DB_API void db_SolveCubic(double *roots,int *nr_roots,double a,double b,double c,double d);
+/*!
+In debug mode closed form quartic solving takes on the order of 0.1 milliseconds
+while eig of the companion matrix takes about 1.5 milliseconds
+Speed-optimized code in release mode solves a quartic in 2.6 microseconds on 450MHz*/
+DB_API void db_SolveQuartic(double *roots,int *nr_roots,double a,double b,double c,double d,double e);
+/*!
+Quartic solving where a solution is forced when splitting into quadratics, which
+can be good if the quartic is sometimes in fact a quadratic, such as in absolute orientation
+when the data is planar*/
+DB_API void db_SolveQuarticForced(double *roots,int *nr_roots,double a,double b,double c,double d,double e);
+
+inline double db_PolyEval1(const double p[2],double x)
+{
+    return(p[0]+x*p[1]);
+}
+
+inline void db_MultiplyPoly1_1(double *d,const double *a,const double *b)
+{
+    double a0,a1;
+    double b0,b1;
+    a0=a[0];a1=a[1];
+    b0=b[0];b1=b[1];
+
+    d[0]=a0*b0;
+    d[1]=a0*b1+a1*b0;
+    d[2]=      a1*b1;
+}
+
+inline void db_MultiplyPoly0_2(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0,b1,b2;
+    a0=a[0];
+    b0=b[0];b1=b[1];b2=b[2];
+
+    d[0]=a0*b0;
+    d[1]=a0*b1;
+    d[2]=a0*b2;
+}
+
+inline void db_MultiplyPoly1_2(double *d,const double *a,const double *b)
+{
+    double a0,a1;
+    double b0,b1,b2;
+    a0=a[0];a1=a[1];
+    b0=b[0];b1=b[1];b2=b[2];
+
+    d[0]=a0*b0;
+    d[1]=a0*b1+a1*b0;
+    d[2]=a0*b2+a1*b1;
+    d[3]=      a1*b2;
+}
+
+
+inline void db_MultiplyPoly1_3(double *d,const double *a,const double *b)
+{
+    double a0,a1;
+    double b0,b1,b2,b3;
+    a0=a[0];a1=a[1];
+    b0=b[0];b1=b[1];b2=b[2];b3=b[3];
+
+    d[0]=a0*b0;
+    d[1]=a0*b1+a1*b0;
+    d[2]=a0*b2+a1*b1;
+    d[3]=a0*b3+a1*b2;
+    d[4]=      a1*b3;
+}
+/*!
+Multiply d=a*b where a is one degree and b is two degree*/
+inline void db_AddPolyProduct0_1(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0,b1;
+    a0=a[0];
+    b0=b[0];b1=b[1];
+
+    d[0]+=a0*b0;
+    d[1]+=a0*b1;
+}
+inline void db_AddPolyProduct0_2(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0,b1,b2;
+    a0=a[0];
+    b0=b[0];b1=b[1];b2=b[2];
+
+    d[0]+=a0*b0;
+    d[1]+=a0*b1;
+    d[2]+=a0*b2;
+}
+/*!
+Multiply d=a*b where a is one degree and b is two degree*/
+inline void db_SubtractPolyProduct0_0(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0;
+    a0=a[0];
+    b0=b[0];
+
+    d[0]-=a0*b0;
+}
+
+inline void db_SubtractPolyProduct0_1(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0,b1;
+    a0=a[0];
+    b0=b[0];b1=b[1];
+
+    d[0]-=a0*b0;
+    d[1]-=a0*b1;
+}
+
+inline void db_SubtractPolyProduct0_2(double *d,const double *a,const double *b)
+{
+    double a0;
+    double b0,b1,b2;
+    a0=a[0];
+    b0=b[0];b1=b[1];b2=b[2];
+
+    d[0]-=a0*b0;
+    d[1]-=a0*b1;
+    d[2]-=a0*b2;
+}
+
+inline void db_SubtractPolyProduct1_3(double *d,const double *a,const double *b)
+{
+    double a0,a1;
+    double b0,b1,b2,b3;
+    a0=a[0];a1=a[1];
+    b0=b[0];b1=b[1];b2=b[2];b3=b[3];
+
+    d[0]-=a0*b0;
+    d[1]-=a0*b1+a1*b0;
+    d[2]-=a0*b2+a1*b1;
+    d[3]-=a0*b3+a1*b2;
+    d[4]-=      a1*b3;
+}
+
+inline void    db_CharacteristicPolynomial4x4(double p[5],const double A[16])
+{
+    /*All two by two determinants of the first two rows*/
+    double two01[3],two02[3],two03[3],two12[3],two13[3],two23[3];
+    /*Polynomials representing third and fourth row of A*/
+    double P0[2],P1[2],P2[2],P3[2];
+    double P4[2],P5[2],P6[2],P7[2];
+    /*All three by three determinants of the first three rows*/
+    double neg_three0[4],neg_three1[4],three2[4],three3[4];
+
+    /*Compute 2x2 determinants*/
+    two01[0]=A[0]*A[5]-A[1]*A[4];
+    two01[1]= -(A[0]+A[5]);
+    two01[2]=1.0;
+
+    two02[0]=A[0]*A[6]-A[2]*A[4];
+    two02[1]= -A[6];
+
+    two03[0]=A[0]*A[7]-A[3]*A[4];
+    two03[1]= -A[7];
+
+    two12[0]=A[1]*A[6]-A[2]*A[5];
+    two12[1]=A[2];
+
+    two13[0]=A[1]*A[7]-A[3]*A[5];
+    two13[1]=A[3];
+
+    two23[0]=A[2]*A[7]-A[3]*A[6];
+
+    P0[0]=A[8];
+    P1[0]=A[9];
+    P2[0]=A[10];P2[1]= -1.0;
+    P3[0]=A[11];
+
+    P4[0]=A[12];
+    P5[0]=A[13];
+    P6[0]=A[14];
+    P7[0]=A[15];P7[1]= -1.0;
+
+    /*Compute 3x3 determinants.Note that the highest
+    degree polynomial goes first and the smaller ones
+    are added or subtracted from it*/
+    db_MultiplyPoly1_1(       neg_three0,P2,two13);
+    db_SubtractPolyProduct0_0(neg_three0,P1,two23);
+    db_SubtractPolyProduct0_1(neg_three0,P3,two12);
+
+    db_MultiplyPoly1_1(       neg_three1,P2,two03);
+    db_SubtractPolyProduct0_1(neg_three1,P3,two02);
+    db_SubtractPolyProduct0_0(neg_three1,P0,two23);
+
+    db_MultiplyPoly0_2(       three2,P3,two01);
+    db_AddPolyProduct0_1(     three2,P0,two13);
+    db_SubtractPolyProduct0_1(three2,P1,two03);
+
+    db_MultiplyPoly1_2(       three3,P2,two01);
+    db_AddPolyProduct0_1(     three3,P0,two12);
+    db_SubtractPolyProduct0_1(three3,P1,two02);
+
+    /*Compute 4x4 determinants*/
+    db_MultiplyPoly1_3(       p,P7,three3);
+    db_AddPolyProduct0_2(     p,P4,neg_three0);
+    db_SubtractPolyProduct0_2(p,P5,neg_three1);
+    db_SubtractPolyProduct0_2(p,P6,three2);
+}
+
+inline void db_RealEigenvalues4x4(double lambda[4],int *nr_roots,const double A[16],int forced=0)
+{
+    double p[5];
+
+    db_CharacteristicPolynomial4x4(p,A);
+    if(forced) db_SolveQuarticForced(lambda,nr_roots,p[4],p[3],p[2],p[1],p[0]);
+     else db_SolveQuartic(lambda,nr_roots,p[4],p[3],p[2],p[1],p[0]);
+}
+
+/*!
+Compute the unit norm eigenvector v of the matrix A corresponding
+to the eigenvalue lambda
+[96mult 60add 1sqrt=156flops 1sqrt]*/
+inline void db_EigenVector4x4(double v[4],double lambda,const double A[16])
+{
+    double a0,a5,a10,a15;
+    double d01,d02,d03,d12,d13,d23;
+    double e01,e02,e03,e12,e13,e23;
+    double C[16],n0,n1,n2,n3,m;
+
+    /*Compute diagonal
+    [4add=4flops]*/
+    a0=A[0]-lambda;
+    a5=A[5]-lambda;
+    a10=A[10]-lambda;
+    a15=A[15]-lambda;
+
+    /*Compute 2x2 determinants of rows 1,2 and 3,4
+    [24mult 12add=36flops]*/
+    d01=a0*a5    -A[1]*A[4];
+    d02=a0*A[6]  -A[2]*A[4];
+    d03=a0*A[7]  -A[3]*A[4];
+    d12=A[1]*A[6]-A[2]*a5;
+    d13=A[1]*A[7]-A[3]*a5;
+    d23=A[2]*A[7]-A[3]*A[6];
+
+    e01=A[8]*A[13]-A[9] *A[12];
+    e02=A[8]*A[14]-a10  *A[12];
+    e03=A[8]*a15  -A[11]*A[12];
+    e12=A[9]*A[14]-a10  *A[13];
+    e13=A[9]*a15  -A[11]*A[13];
+    e23=a10 *a15  -A[11]*A[14];
+
+    /*Compute matrix of cofactors
+    [48mult 32 add=80flops*/
+    C[0]=  (a5  *e23-A[6]*e13+A[7]*e12);
+    C[1]= -(A[4]*e23-A[6]*e03+A[7]*e02);
+    C[2]=  (A[4]*e13-a5  *e03+A[7]*e01);
+    C[3]= -(A[4]*e12-a5  *e02+A[6]*e01);
+
+    C[4]= -(A[1]*e23-A[2]*e13+A[3]*e12);
+    C[5]=  (a0  *e23-A[2]*e03+A[3]*e02);
+    C[6]= -(a0  *e13-A[1]*e03+A[3]*e01);
+    C[7]=  (a0  *e12-A[1]*e02+A[2]*e01);
+
+    C[8]=   (A[13]*d23-A[14]*d13+a15  *d12);
+    C[9]=  -(A[12]*d23-A[14]*d03+a15  *d02);
+    C[10]=  (A[12]*d13-A[13]*d03+a15  *d01);
+    C[11]= -(A[12]*d12-A[13]*d02+A[14]*d01);
+
+    C[12]= -(A[9]*d23-a10 *d13+A[11]*d12);
+    C[13]=  (A[8]*d23-a10 *d03+A[11]*d02);
+    C[14]= -(A[8]*d13-A[9]*d03+A[11]*d01);
+    C[15]=  (A[8]*d12-A[9]*d02+a10  *d01);
+
+    /*Compute square sums of rows
+    [16mult 12add=28flops*/
+    n0=db_sqr(C[0]) +db_sqr(C[1]) +db_sqr(C[2]) +db_sqr(C[3]);
+    n1=db_sqr(C[4]) +db_sqr(C[5]) +db_sqr(C[6]) +db_sqr(C[7]);
+    n2=db_sqr(C[8]) +db_sqr(C[9]) +db_sqr(C[10])+db_sqr(C[11]);
+    n3=db_sqr(C[12])+db_sqr(C[13])+db_sqr(C[14])+db_sqr(C[15]);
+
+    /*Take the largest norm row and normalize
+    [4mult 1 sqrt=4flops 1sqrt]*/
+    if(n0>=n1 && n0>=n2 && n0>=n3)
+    {
+        m=db_SafeReciprocal(sqrt(n0));
+        db_MultiplyScalarCopy4(v,C,m);
+    }
+    else if(n1>=n2 && n1>=n3)
+    {
+        m=db_SafeReciprocal(sqrt(n1));
+        db_MultiplyScalarCopy4(v,&(C[4]),m);
+    }
+    else if(n2>=n3)
+    {
+        m=db_SafeReciprocal(sqrt(n2));
+        db_MultiplyScalarCopy4(v,&(C[8]),m);
+    }
+    else
+    {
+        m=db_SafeReciprocal(sqrt(n3));
+        db_MultiplyScalarCopy4(v,&(C[12]),m);
+    }
+}
+
+
+
+/*\}*/
+#endif /* DB_UTILITIES_POLY */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_random.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_random.h
new file mode 100644
index 0000000..ef24039
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_random.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_random.h,v 1.1 2010/08/19 18:09:20 bsouthall Exp $ */
+
+#ifndef DB_UTILITIES_RANDOM
+#define DB_UTILITIES_RANDOM
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMRandom (LM) Random numbers, random sampling
+ */
+/*\{*/
+/*!
+ Random Number generator. Initialize with non-zero
+integer value r. A double between zero and one is
+returned.
+\param r    seed
+\return random double
+*/
+inline double db_QuickRandomDouble(int &r)
+{
+    int c;
+    c=r/127773;
+    r=16807*(r-c*127773)-2836*c;
+    if(r<0) r+=2147483647;
+    return((1.0/((double)2147483647))*r);
+    //return (((double)rand())/(double)RAND_MAX);
+}
+
+/*!
+Random Number generator. Initialize with non-zero
+integer value r. An int between and including 0 and max
+ \param r    seed
+ \param max    upped limit
+ \return random int
+*/
+inline int db_RandomInt(int &r,int max)
+{
+    double dtemp;
+    int itemp;
+    dtemp=db_QuickRandomDouble(r)*(max+1);
+    itemp=(int) dtemp;
+    if(itemp<=0) return(0);
+    if(itemp>=max) return(max);
+    return(itemp);
+}
+
+/*!
+ Generate a random sample indexing into [0..pool_size-1].
+ \param s            sample (out) pre-allocated array of size sample_size
+ \param sample_size    size of sample
+ \param pool_size    upper limit on item index
+ \param r_seed        random number generator seed
+ */
+inline void db_RandomSample(int *s,int sample_size,int pool_size,int &r_seed)
+{
+    int temp,temp2,i,j;
+
+    for(i=0;i<sample_size;i++)
+    {
+        temp=db_RandomInt(r_seed,pool_size-1-i);
+
+        for(j=0;j<i;j++)
+        {
+            if(s[j]<=temp) temp++;
+            else
+            {
+                /*swap*/
+                temp2=temp;
+                temp=s[j];
+                s[j]=temp2;
+            }
+        }
+        s[i]=temp;
+    }
+}
+/*\}*/
+#endif /* DB_UTILITIES_RANDOM */
diff --git a/jni_mosaic/feature_stab/db_vlvm/db_utilities_rotation.h b/jni_mosaic/feature_stab/db_vlvm/db_utilities_rotation.h
new file mode 100644
index 0000000..7f5f937
--- /dev/null
+++ b/jni_mosaic/feature_stab/db_vlvm/db_utilities_rotation.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/* $Id: db_utilities_rotation.h,v 1.2 2010/09/03 12:00:11 bsouthall Exp $ */
+
+#ifndef DB_UTILITIES_ROTATION
+#define DB_UTILITIES_ROTATION
+
+#include "db_utilities.h"
+
+
+
+/*****************************************************************
+*    Lean and mean begins here                                   *
+*****************************************************************/
+/*!
+ * \defgroup LMRotation (LM) Rotation Utilities (quaternions, orthonormal)
+ */
+/*\{*/
+/*!
+ Takes a unit quaternion and gives its corresponding rotation matrix.
+ \param R rotation matrix (out)
+ \param q quaternion
+ */
+inline void db_QuaternionToRotation(double R[9],const double q[4])
+{
+    double q0q0,q0qx,q0qy,q0qz,qxqx,qxqy,qxqz,qyqy,qyqz,qzqz;
+
+    q0q0=q[0]*q[0];
+    q0qx=q[0]*q[1];
+    q0qy=q[0]*q[2];
+    q0qz=q[0]*q[3];
+    qxqx=q[1]*q[1];
+    qxqy=q[1]*q[2];
+    qxqz=q[1]*q[3];
+    qyqy=q[2]*q[2];
+    qyqz=q[2]*q[3];
+    qzqz=q[3]*q[3];
+
+    R[0]=q0q0+qxqx-qyqy-qzqz; R[1]=2.0*(qxqy-q0qz);     R[2]=2.0*(qxqz+q0qy);
+    R[3]=2.0*(qxqy+q0qz);     R[4]=q0q0-qxqx+qyqy-qzqz; R[5]=2.0*(qyqz-q0qx);
+    R[6]=2.0*(qxqz-q0qy);     R[7]=2.0*(qyqz+q0qx);     R[8]=q0q0-qxqx-qyqy+qzqz;
+}
+
+/*\}*/
+#endif /* DB_UTILITIES_ROTATION */
diff --git a/jni_mosaic/feature_stab/doc/Readme.txt b/jni_mosaic/feature_stab/doc/Readme.txt
new file mode 100644
index 0000000..fcd7c38
--- /dev/null
+++ b/jni_mosaic/feature_stab/doc/Readme.txt
@@ -0,0 +1,3 @@
+To generate the html docs, execute
+doxygen dbreg_API_doxyfile
+
diff --git a/jni_mosaic/feature_stab/doc/dbreg_API_doxyfile b/jni_mosaic/feature_stab/doc/dbreg_API_doxyfile
new file mode 100755
index 0000000..dc61a9c
--- /dev/null
+++ b/jni_mosaic/feature_stab/doc/dbreg_API_doxyfile
@@ -0,0 +1,1557 @@
+# Doxyfile 1.6.1
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           =
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = .
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        = /Users/dimitri/doxygen/mail/1.5.7/doxywizard/
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set
+# FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penality.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will rougly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.  This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = ../src/dbreg/dbreg.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.d \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.cs \
+                         *.php \
+                         *.php3 \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.f90 \
+                         *.f \
+                         *.vhd \
+                         *.vhdl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.  Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.  The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          =
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# When the SEARCHENGINE tag is enable doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
+# there is already a search function so this one should typically
+# be disabled.
+
+SEARCHENGINE           = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.  This is useful
+# if you want to understand what is going on.  On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#   TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/jni_mosaic/feature_stab/src/dbreg/dbreg.cpp b/jni_mosaic/feature_stab/src/dbreg/dbreg.cpp
new file mode 100644
index 0000000..da06aa2
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/dbreg.cpp
@@ -0,0 +1,794 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// $Id: dbreg.cpp,v 1.31 2011/06/17 14:04:32 mbansal Exp $
+#include "dbreg.h"
+#include <string.h>
+#include <stdio.h>
+
+
+#if PROFILE
+#endif
+
+//#include <iostream>
+
+db_FrameToReferenceRegistration::db_FrameToReferenceRegistration() :
+  m_initialized(false),m_nr_matches(0),m_over_allocation(256),m_nr_bins(20),m_max_cost_pix(30), m_quarter_resolution(false)
+{
+  m_reference_image = NULL;
+  m_aligned_ins_image = NULL;
+
+  m_quarter_res_image = NULL;
+  m_horz_smooth_subsample_image = NULL;
+
+  m_x_corners_ref = NULL;
+  m_y_corners_ref = NULL;
+
+  m_x_corners_ins = NULL;
+  m_y_corners_ins = NULL;
+
+  m_match_index_ref = NULL;
+  m_match_index_ins = NULL;
+
+  m_inlier_indices = NULL;
+
+  m_num_inlier_indices = 0;
+
+  m_temp_double = NULL;
+  m_temp_int = NULL;
+
+  m_corners_ref = NULL;
+  m_corners_ins = NULL;
+
+  m_sq_cost = NULL;
+  m_cost_histogram = NULL;
+
+  profile_string = NULL;
+
+  db_Identity3x3(m_K);
+  db_Identity3x3(m_H_ref_to_ins);
+  db_Identity3x3(m_H_dref_to_ref);
+
+  m_sq_cost_computed = false;
+  m_reference_set = false;
+
+  m_reference_update_period = 0;
+  m_nr_frames_processed = 0;
+
+  return;
+}
+
+db_FrameToReferenceRegistration::~db_FrameToReferenceRegistration()
+{
+  Clean();
+}
+
+void db_FrameToReferenceRegistration::Clean()
+{
+  if ( m_reference_image )
+    db_FreeImage_u(m_reference_image,m_im_height);
+
+  if ( m_aligned_ins_image )
+    db_FreeImage_u(m_aligned_ins_image,m_im_height);
+
+  if ( m_quarter_res_image )
+  {
+    db_FreeImage_u(m_quarter_res_image, m_im_height);
+  }
+
+  if ( m_horz_smooth_subsample_image )
+  {
+    db_FreeImage_u(m_horz_smooth_subsample_image, m_im_height*2);
+  }
+
+  delete [] m_x_corners_ref;
+  delete [] m_y_corners_ref;
+
+  delete [] m_x_corners_ins;
+  delete [] m_y_corners_ins;
+
+  delete [] m_match_index_ref;
+  delete [] m_match_index_ins;
+
+  delete [] m_temp_double;
+  delete [] m_temp_int;
+
+  delete [] m_corners_ref;
+  delete [] m_corners_ins;
+
+  delete [] m_sq_cost;
+  delete [] m_cost_histogram;
+
+  delete [] m_inlier_indices;
+
+  if(profile_string)
+    delete [] profile_string;
+
+  m_reference_image = NULL;
+  m_aligned_ins_image = NULL;
+
+  m_quarter_res_image = NULL;
+  m_horz_smooth_subsample_image = NULL;
+
+  m_x_corners_ref = NULL;
+  m_y_corners_ref = NULL;
+
+  m_x_corners_ins = NULL;
+  m_y_corners_ins = NULL;
+
+  m_match_index_ref = NULL;
+  m_match_index_ins = NULL;
+
+  m_inlier_indices = NULL;
+
+  m_temp_double = NULL;
+  m_temp_int = NULL;
+
+  m_corners_ref = NULL;
+  m_corners_ins = NULL;
+
+  m_sq_cost = NULL;
+  m_cost_histogram = NULL;
+}
+
+void db_FrameToReferenceRegistration::Init(int width, int height,
+                       int    homography_type,
+                       int    max_iterations,
+                       bool   linear_polish,
+                       bool   quarter_resolution,
+                       double scale,
+                       unsigned int reference_update_period,
+                       bool   do_motion_smoothing,
+                       double motion_smoothing_gain,
+                       int    nr_samples,
+                       int    chunk_size,
+                       int    cd_target_nr_corners,
+                       double cm_max_disparity,
+                           bool   cm_use_smaller_matching_window,
+                       int    cd_nr_horz_blocks,
+                       int    cd_nr_vert_blocks
+                       )
+{
+  Clean();
+
+  m_reference_update_period = reference_update_period;
+  m_nr_frames_processed = 0;
+
+  m_do_motion_smoothing = do_motion_smoothing;
+  m_motion_smoothing_gain = motion_smoothing_gain;
+
+  m_stab_smoother.setSmoothingFactor(m_motion_smoothing_gain);
+
+  m_quarter_resolution = quarter_resolution;
+
+  profile_string = new char[10240];
+
+  if (m_quarter_resolution == true)
+  {
+    width = width/2;
+    height = height/2;
+
+    m_horz_smooth_subsample_image = db_AllocImage_u(width,height*2,m_over_allocation);
+    m_quarter_res_image = db_AllocImage_u(width,height,m_over_allocation);
+  }
+
+  m_im_width = width;
+  m_im_height = height;
+
+  double temp[9];
+  db_Approx3DCalMat(m_K,temp,m_im_width,m_im_height);
+
+  m_homography_type = homography_type;
+  m_max_iterations = max_iterations;
+  m_scale = 2/(m_K[0]+m_K[4]);
+  m_nr_samples = nr_samples;
+  m_chunk_size = chunk_size;
+
+  double outlier_t1 = 5.0;
+
+  m_outlier_t2 = outlier_t1*outlier_t1;//*m_scale*m_scale;
+
+  m_current_is_reference = false;
+
+  m_linear_polish = linear_polish;
+
+  m_reference_image = db_AllocImage_u(m_im_width,m_im_height,m_over_allocation);
+  m_aligned_ins_image = db_AllocImage_u(m_im_width,m_im_height,m_over_allocation);
+
+  // initialize feature detection and matching:
+  //m_max_nr_corners = m_cd.Init(m_im_width,m_im_height,cd_target_nr_corners,cd_nr_horz_blocks,cd_nr_vert_blocks,0.0,0.0);
+  m_max_nr_corners = m_cd.Init(m_im_width,m_im_height,cd_target_nr_corners,cd_nr_horz_blocks,cd_nr_vert_blocks,DB_DEFAULT_ABS_CORNER_THRESHOLD/500.0,0.0);
+
+    int use_21 = 0;
+  m_max_nr_matches = m_cm.Init(m_im_width,m_im_height,cm_max_disparity,m_max_nr_corners,DB_DEFAULT_NO_DISPARITY,cm_use_smaller_matching_window,use_21);
+
+  // allocate space for corner feature locations for reference and inspection images:
+  m_x_corners_ref = new double [m_max_nr_corners];
+  m_y_corners_ref = new double [m_max_nr_corners];
+
+  m_x_corners_ins = new double [m_max_nr_corners];
+  m_y_corners_ins = new double [m_max_nr_corners];
+
+  // allocate space for match indices:
+  m_match_index_ref = new int [m_max_nr_matches];
+  m_match_index_ins = new int [m_max_nr_matches];
+
+  m_temp_double = new double [12*DB_DEFAULT_NR_SAMPLES+10*m_max_nr_matches];
+  m_temp_int = new int [db_maxi(DB_DEFAULT_NR_SAMPLES,m_max_nr_matches)];
+
+  // allocate space for homogenous image points:
+  m_corners_ref = new double [3*m_max_nr_corners];
+  m_corners_ins = new double [3*m_max_nr_corners];
+
+  // allocate cost array and histogram:
+  m_sq_cost = new double [m_max_nr_matches];
+  m_cost_histogram = new int [m_nr_bins];
+
+  // reserve array:
+  //m_inlier_indices.reserve(m_max_nr_matches);
+  m_inlier_indices = new int[m_max_nr_matches];
+
+  m_initialized = true;
+
+  m_max_inlier_count = 0;
+}
+
+
+#define MB 0
+// Save the reference image, detect features and update the dref-to-ref transformation
+int db_FrameToReferenceRegistration::UpdateReference(const unsigned char * const * im, bool subsample, bool detect_corners)
+{
+  double temp[9];
+  db_Multiply3x3_3x3(temp,m_H_dref_to_ref,m_H_ref_to_ins);
+  db_Copy9(m_H_dref_to_ref,temp);
+
+  const unsigned char * const * imptr = im;
+
+  if (m_quarter_resolution && subsample)
+  {
+    GenerateQuarterResImage(im);
+    imptr = m_quarter_res_image;
+  }
+
+  // save the reference image, detect features and quit
+  db_CopyImage_u(m_reference_image,imptr,m_im_width,m_im_height,m_over_allocation);
+
+  if(detect_corners)
+  {
+    #if MB
+    m_cd.DetectCorners(imptr, m_x_corners_ref,m_y_corners_ref,&m_nr_corners_ref);
+    int nr = 0;
+    for(int k=0; k<m_nr_corners_ref; k++)
+    {
+        if(m_x_corners_ref[k]>m_im_width/3)
+        {
+            m_x_corners_ref[nr] = m_x_corners_ref[k];
+            m_y_corners_ref[nr] = m_y_corners_ref[k];
+            nr++;
+        }
+
+    }
+    m_nr_corners_ref = nr;
+    #else
+    m_cd.DetectCorners(imptr, m_x_corners_ref,m_y_corners_ref,&m_nr_corners_ref);
+    #endif
+  }
+  else
+  {
+    m_nr_corners_ref = m_nr_corners_ins;
+
+    for(int k=0; k<m_nr_corners_ins; k++)
+    {
+        m_x_corners_ref[k] = m_x_corners_ins[k];
+        m_y_corners_ref[k] = m_y_corners_ins[k];
+    }
+
+  }
+
+  db_Identity3x3(m_H_ref_to_ins);
+
+  m_max_inlier_count = 0;   // Reset to 0 as no inliers seen until now
+  m_sq_cost_computed = false;
+  m_reference_set = true;
+  m_current_is_reference = true;
+  return 1;
+}
+
+void db_FrameToReferenceRegistration::Get_H_dref_to_ref(double H[9])
+{
+  db_Copy9(H,m_H_dref_to_ref);
+}
+
+void db_FrameToReferenceRegistration::Get_H_dref_to_ins(double H[9])
+{
+  db_Multiply3x3_3x3(H,m_H_dref_to_ref,m_H_ref_to_ins);
+}
+
+void db_FrameToReferenceRegistration::Set_H_dref_to_ins(double H[9])
+{
+    double H_ins_to_ref[9];
+
+    db_Identity3x3(H_ins_to_ref);   // Ensure it has proper values
+    db_InvertAffineTransform(H_ins_to_ref,m_H_ref_to_ins);  // Invert to get ins to ref
+    db_Multiply3x3_3x3(m_H_dref_to_ref,H,H_ins_to_ref); // Update dref to ref using the input H from dref to ins
+}
+
+
+void db_FrameToReferenceRegistration::ResetDisplayReference()
+{
+  db_Identity3x3(m_H_dref_to_ref);
+}
+
+bool db_FrameToReferenceRegistration::NeedReferenceUpdate()
+{
+  // If less than 50% of the starting number of inliers left, then its time to update the reference.
+  if(m_max_inlier_count>0 && float(m_num_inlier_indices)/float(m_max_inlier_count)<0.5)
+    return true;
+  else
+    return false;
+}
+
+int db_FrameToReferenceRegistration::AddFrame(const unsigned char * const * im, double H[9],bool force_reference,bool prewarp)
+{
+  m_current_is_reference = false;
+  if(!m_reference_set || force_reference)
+    {
+      db_Identity3x3(m_H_ref_to_ins);
+      db_Copy9(H,m_H_ref_to_ins);
+
+      UpdateReference(im,true,true);
+      return 0;
+    }
+
+  const unsigned char * const * imptr = im;
+
+  if (m_quarter_resolution)
+  {
+    if (m_quarter_res_image)
+    {
+      GenerateQuarterResImage(im);
+    }
+
+    imptr = (const unsigned char * const* )m_quarter_res_image;
+  }
+
+  double H_last[9];
+  db_Copy9(H_last,m_H_ref_to_ins);
+  db_Identity3x3(m_H_ref_to_ins);
+
+  m_sq_cost_computed = false;
+
+  // detect corners on inspection image and match to reference image features:s
+
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+#if PROFILE
+  double iTimer1, iTimer2;
+  char str[255];
+  strcpy(profile_string,"\n");
+  sprintf(str,"[%dx%d] %p\n",m_im_width,m_im_height,im);
+  strcat(profile_string, str);
+#endif
+
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+#if PROFILE
+  iTimer1 = now_ms();
+#endif
+  m_cd.DetectCorners(imptr, m_x_corners_ins,m_y_corners_ins,&m_nr_corners_ins);
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+# if PROFILE
+  iTimer2 = now_ms();
+  double elapsedTimeCorner = iTimer2 - iTimer1;
+  sprintf(str,"Corner Detection [%d corners] = %g ms\n",m_nr_corners_ins, elapsedTimeCorner);
+  strcat(profile_string, str);
+#endif
+
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+#if PROFILE
+  iTimer1 = now_ms();
+#endif
+    if(prewarp)
+  m_cm.Match(m_reference_image,imptr,m_x_corners_ref,m_y_corners_ref,m_nr_corners_ref,
+         m_x_corners_ins,m_y_corners_ins,m_nr_corners_ins,
+         m_match_index_ref,m_match_index_ins,&m_nr_matches,H,0);
+    else
+  m_cm.Match(m_reference_image,imptr,m_x_corners_ref,m_y_corners_ref,m_nr_corners_ref,
+         m_x_corners_ins,m_y_corners_ins,m_nr_corners_ins,
+         m_match_index_ref,m_match_index_ins,&m_nr_matches);
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+# if PROFILE
+  iTimer2 = now_ms();
+  double elapsedTimeMatch = iTimer2 - iTimer1;
+  sprintf(str,"Matching [%d] = %g ms\n",m_nr_matches,elapsedTimeMatch);
+  strcat(profile_string, str);
+#endif
+
+
+  // copy out matching features:
+  for ( int i = 0; i < m_nr_matches; ++i )
+    {
+      int offset = 3*i;
+      m_corners_ref[offset  ] = m_x_corners_ref[m_match_index_ref[i]];
+      m_corners_ref[offset+1] = m_y_corners_ref[m_match_index_ref[i]];
+      m_corners_ref[offset+2] = 1.0;
+
+      m_corners_ins[offset  ] = m_x_corners_ins[m_match_index_ins[i]];
+      m_corners_ins[offset+1] = m_y_corners_ins[m_match_index_ins[i]];
+      m_corners_ins[offset+2] = 1.0;
+    }
+
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+#if PROFILE
+  iTimer1 = now_ms();
+#endif
+  // perform the alignment:
+  db_RobImageHomography(m_H_ref_to_ins, m_corners_ref, m_corners_ins, m_nr_matches, m_K, m_K, m_temp_double, m_temp_int,
+            m_homography_type,NULL,m_max_iterations,m_max_nr_matches,m_scale,
+            m_nr_samples, m_chunk_size);
+  // @jke - Adding code to time the functions.  TODO: Remove after test
+# if PROFILE
+  iTimer2 = now_ms();
+  double elapsedTimeHomography = iTimer2 - iTimer1;
+  sprintf(str,"Homography = %g ms\n",elapsedTimeHomography);
+  strcat(profile_string, str);
+#endif
+
+
+  SetOutlierThreshold();
+
+  // Compute the inliers for the db compute m_H_ref_to_ins
+  ComputeInliers(m_H_ref_to_ins);
+
+  // Update the max inlier count
+  m_max_inlier_count = (m_max_inlier_count > m_num_inlier_indices)?m_max_inlier_count:m_num_inlier_indices;
+
+  // Fit a least-squares model to just the inliers and put it in m_H_ref_to_ins
+  if(m_linear_polish)
+    Polish(m_inlier_indices, m_num_inlier_indices);
+
+  if (m_quarter_resolution)
+  {
+    m_H_ref_to_ins[2] *= 2.0;
+    m_H_ref_to_ins[5] *= 2.0;
+  }
+
+#if PROFILE
+  sprintf(str,"#Inliers = %d \n",m_num_inlier_indices);
+  strcat(profile_string, str);
+#endif
+/*
+  ///// CHECK IF CURRENT TRANSFORMATION GOOD OR BAD ////
+  ///// IF BAD, then update reference to the last correctly aligned inspection frame;
+  if(m_num_inlier_indices<5)//0.9*m_nr_matches || m_nr_matches < 20)
+  {
+    db_Copy9(m_H_ref_to_ins,H_last);
+    UpdateReference(imptr,false);
+//  UpdateReference(m_aligned_ins_image,false);
+  }
+  else
+  {
+  ///// IF GOOD, then update the last correctly aligned inspection frame to be this;
+  //db_CopyImage_u(m_aligned_ins_image,imptr,m_im_width,m_im_height,m_over_allocation);
+*/
+  if(m_do_motion_smoothing)
+    SmoothMotion();
+
+   // Disable debug printing
+   // db_PrintDoubleMatrix(m_H_ref_to_ins,3,3);
+
+  db_Copy9(H, m_H_ref_to_ins);
+
+  m_nr_frames_processed++;
+{
+  if ( (m_nr_frames_processed % m_reference_update_period) == 0 )
+  {
+    //UpdateReference(imptr,false, false);
+
+    #if MB
+    UpdateReference(imptr,false, true);
+    #else
+    UpdateReference(imptr,false, false);
+    #endif
+  }
+
+
+  }
+
+
+
+  return 1;
+}
+
+//void db_FrameToReferenceRegistration::ComputeInliers(double H[9],std::vector<int> &inlier_indices)
+void db_FrameToReferenceRegistration::ComputeInliers(double H[9])
+{
+  double totnummatches = m_nr_matches;
+  int inliercount=0;
+
+  m_num_inlier_indices = 0;
+//  inlier_indices.clear();
+
+  for(int c=0; c < totnummatches; c++ )
+    {
+      if (m_sq_cost[c] <= m_outlier_t2)
+    {
+      m_inlier_indices[inliercount] = c;
+      inliercount++;
+    }
+    }
+
+  m_num_inlier_indices = inliercount;
+  double frac=inliercount/totnummatches;
+}
+
+//void db_FrameToReferenceRegistration::Polish(std::vector<int> &inlier_indices)
+void db_FrameToReferenceRegistration::Polish(int *inlier_indices, int &num_inlier_indices)
+{
+  db_Zero(m_polish_C,36);
+  db_Zero(m_polish_D,6);
+  for (int i=0;i<num_inlier_indices;i++)
+    {
+      int j = 3*inlier_indices[i];
+      m_polish_C[0]+=m_corners_ref[j]*m_corners_ref[j];
+      m_polish_C[1]+=m_corners_ref[j]*m_corners_ref[j+1];
+      m_polish_C[2]+=m_corners_ref[j];
+      m_polish_C[7]+=m_corners_ref[j+1]*m_corners_ref[j+1];
+      m_polish_C[8]+=m_corners_ref[j+1];
+      m_polish_C[14]+=1;
+      m_polish_D[0]+=m_corners_ref[j]*m_corners_ins[j];
+      m_polish_D[1]+=m_corners_ref[j+1]*m_corners_ins[j];
+      m_polish_D[2]+=m_corners_ins[j];
+      m_polish_D[3]+=m_corners_ref[j]*m_corners_ins[j+1];
+      m_polish_D[4]+=m_corners_ref[j+1]*m_corners_ins[j+1];
+      m_polish_D[5]+=m_corners_ins[j+1];
+    }
+
+  double a=db_maxd(m_polish_C[0],m_polish_C[7]);
+  m_polish_C[0]/=a; m_polish_C[1]/=a;   m_polish_C[2]/=a;
+  m_polish_C[7]/=a; m_polish_C[8]/=a; m_polish_C[14]/=a;
+
+  m_polish_D[0]/=a; m_polish_D[1]/=a;   m_polish_D[2]/=a;
+  m_polish_D[3]/=a; m_polish_D[4]/=a;   m_polish_D[5]/=a;
+
+
+  m_polish_C[6]=m_polish_C[1];
+  m_polish_C[12]=m_polish_C[2];
+  m_polish_C[13]=m_polish_C[8];
+
+  m_polish_C[21]=m_polish_C[0]; m_polish_C[22]=m_polish_C[1]; m_polish_C[23]=m_polish_C[2];
+  m_polish_C[28]=m_polish_C[7]; m_polish_C[29]=m_polish_C[8];
+  m_polish_C[35]=m_polish_C[14];
+
+
+  double d[6];
+  db_CholeskyDecomp6x6(m_polish_C,d);
+  db_CholeskyBacksub6x6(m_H_ref_to_ins,m_polish_C,d,m_polish_D);
+}
+
+void db_FrameToReferenceRegistration::EstimateSecondaryModel(double H[9])
+{
+  /*      if ( m_current_is_reference )
+      {
+      db_Identity3x3(H);
+      return;
+      }
+  */
+
+  // select the outliers of the current model:
+  SelectOutliers();
+
+  // perform the alignment:
+  db_RobImageHomography(m_H_ref_to_ins, m_corners_ref, m_corners_ins, m_nr_matches, m_K, m_K, m_temp_double, m_temp_int,
+            m_homography_type,NULL,m_max_iterations,m_max_nr_matches,m_scale,
+            m_nr_samples, m_chunk_size);
+
+  db_Copy9(H,m_H_ref_to_ins);
+}
+
+void db_FrameToReferenceRegistration::ComputeCostArray()
+{
+  if ( m_sq_cost_computed ) return;
+
+  for( int c=0, k=0 ;c < m_nr_matches; c++, k=k+3)
+    {
+      m_sq_cost[c] = SquaredInhomogenousHomographyError(m_corners_ins+k,m_H_ref_to_ins,m_corners_ref+k);
+    }
+
+  m_sq_cost_computed = true;
+}
+
+void db_FrameToReferenceRegistration::SelectOutliers()
+{
+  int nr_outliers=0;
+
+  ComputeCostArray();
+
+  for(int c=0, k=0 ;c<m_nr_matches;c++,k=k+3)
+    {
+      if (m_sq_cost[c] > m_outlier_t2)
+    {
+      int offset = 3*nr_outliers++;
+      db_Copy3(m_corners_ref+offset,m_corners_ref+k);
+      db_Copy3(m_corners_ins+offset,m_corners_ins+k);
+    }
+    }
+
+  m_nr_matches = nr_outliers;
+}
+
+void db_FrameToReferenceRegistration::ComputeCostHistogram()
+{
+  ComputeCostArray();
+
+  for ( int b = 0; b < m_nr_bins; ++b )
+    m_cost_histogram[b] = 0;
+
+  for(int c = 0; c < m_nr_matches; c++)
+    {
+      double error = db_SafeSqrt(m_sq_cost[c]);
+      int bin = (int)(error/m_max_cost_pix*m_nr_bins);
+      if ( bin < m_nr_bins )
+    m_cost_histogram[bin]++;
+      else
+    m_cost_histogram[m_nr_bins-1]++;
+    }
+
+/*
+  for ( int i = 0; i < m_nr_bins; ++i )
+    std::cout << m_cost_histogram[i] << " ";
+  std::cout << std::endl;
+*/
+}
+
+void db_FrameToReferenceRegistration::SetOutlierThreshold()
+{
+  ComputeCostHistogram();
+
+  int i = 0, last=0;
+  for (; i < m_nr_bins-1; ++i )
+    {
+      if ( last > m_cost_histogram[i] )
+    break;
+      last = m_cost_histogram[i];
+    }
+
+  //std::cout << "I " <<  i << std::endl;
+
+  int max = m_cost_histogram[i];
+
+  for (; i < m_nr_bins-1; ++i )
+    {
+      if ( m_cost_histogram[i] < (int)(0.1*max) )
+    //if ( last < m_cost_histogram[i] )
+    break;
+      last = m_cost_histogram[i];
+    }
+  //std::cout << "J " <<  i << std::endl;
+
+  m_outlier_t2 = db_sqr(i*m_max_cost_pix/m_nr_bins);
+
+  //std::cout << "m_outlier_t2 " <<  m_outlier_t2 << std::endl;
+}
+
+void db_FrameToReferenceRegistration::SmoothMotion(void)
+{
+  VP_MOTION inmot,outmot;
+
+  double H[9];
+
+  Get_H_dref_to_ins(H);
+
+      MXX(inmot) = H[0];
+    MXY(inmot) = H[1];
+    MXZ(inmot) = H[2];
+    MXW(inmot) = 0.0;
+
+    MYX(inmot) = H[3];
+    MYY(inmot) = H[4];
+    MYZ(inmot) = H[5];
+    MYW(inmot) = 0.0;
+
+    MZX(inmot) = H[6];
+    MZY(inmot) = H[7];
+    MZZ(inmot) = H[8];
+    MZW(inmot) = 0.0;
+
+    MWX(inmot) = 0.0;
+    MWY(inmot) = 0.0;
+    MWZ(inmot) = 0.0;
+    MWW(inmot) = 1.0;
+
+    inmot.type = VP_MOTION_AFFINE;
+
+    int w = m_im_width;
+    int h = m_im_height;
+
+    if(m_quarter_resolution)
+    {
+    w = w*2;
+    h = h*2;
+    }
+
+#if 0
+    m_stab_smoother.smoothMotionAdaptive(w,h,&inmot,&outmot);
+#else
+    m_stab_smoother.smoothMotion(&inmot,&outmot);
+#endif
+
+    H[0] = MXX(outmot);
+    H[1] = MXY(outmot);
+    H[2] = MXZ(outmot);
+
+    H[3] = MYX(outmot);
+    H[4] = MYY(outmot);
+    H[5] = MYZ(outmot);
+
+    H[6] = MZX(outmot);
+    H[7] = MZY(outmot);
+    H[8] = MZZ(outmot);
+
+    Set_H_dref_to_ins(H);
+}
+
+void db_FrameToReferenceRegistration::GenerateQuarterResImage(const unsigned char* const* im)
+{
+  int input_h = m_im_height*2;
+  int input_w = m_im_width*2;
+
+  for (int j = 0; j < input_h; j++)
+  {
+    const unsigned char* in_row_ptr = im[j];
+    unsigned char* out_row_ptr = m_horz_smooth_subsample_image[j]+1;
+
+    for (int i = 2; i < input_w-2; i += 2)
+    {
+      int smooth_val = (
+            6*in_row_ptr[i] +
+            ((in_row_ptr[i-1]+in_row_ptr[i+1])<<2) +
+            in_row_ptr[i-2]+in_row_ptr[i+2]
+            ) >> 4;
+      *out_row_ptr++ = (unsigned char) smooth_val;
+
+      if ( (smooth_val < 0) || (smooth_val > 255))
+      {
+        return;
+      }
+
+    }
+  }
+
+  for (int j = 2; j < input_h-2; j+=2)
+  {
+
+    unsigned char* in_row_ptr = m_horz_smooth_subsample_image[j];
+    unsigned char* out_row_ptr = m_quarter_res_image[j/2];
+
+    for (int i = 1; i < m_im_width-1; i++)
+    {
+      int smooth_val = (
+            6*in_row_ptr[i] +
+            ((in_row_ptr[i-m_im_width]+in_row_ptr[i+m_im_width]) << 2)+
+            in_row_ptr[i-2*m_im_width]+in_row_ptr[i+2*m_im_width]
+            ) >> 4;
+      *out_row_ptr++ = (unsigned char)smooth_val;
+
+      if ( (smooth_val < 0) || (smooth_val > 255))
+      {
+        return;
+      }
+
+    }
+  }
+}
diff --git a/jni_mosaic/feature_stab/src/dbreg/dbreg.h b/jni_mosaic/feature_stab/src/dbreg/dbreg.h
new file mode 100644
index 0000000..4eb2444
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/dbreg.h
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+
+#pragma once
+
+#ifdef _WIN32
+#ifdef DBREG_EXPORTS
+#define DBREG_API __declspec(dllexport)
+#else
+#define DBREG_API __declspec(dllimport)
+#endif
+#else
+#define DBREG_API
+#endif
+
+// @jke - the next few lines are for extracting timing data.  TODO: Remove after test
+#define PROFILE 0
+
+#include "dbstabsmooth.h"
+
+#include <db_feature_detection.h>
+#include <db_feature_matching.h>
+#include <db_rob_image_homography.h>
+
+#if PROFILE
+    #include <sys/time.h>
+#endif
+
+/*! \mainpage db_FrameToReferenceRegistration
+
+ \section intro Introduction
+
+ db_FrameToReferenceRegistration provides a simple interface to a set of sophisticated algorithms for stabilizing
+ video sequences.  As its name suggests, the class is used to compute parameters that will allow us to warp incoming video
+ frames and register them with respect to a so-called <i>reference</i> frame.  The reference frame is simply the first
+ frame of a sequence; the registration process is that of estimating the parameters of a warp that can be applied to
+ subsequent frames to make those frames align with the reference.  A video made up of these warped frames will be more
+ stable than the input video.
+
+ For more technical information on the internal structure of the algorithms used within the db_FrameToRegistration class,
+ please follow this <a href="../Sarnoff image registration.docx">link</a>.
+
+ \section usage Usage
+ In addition to the class constructor, there are two main functions of db_FrameToReferenceRegistration that are of
+ interest to the programmer.  db_FrameToReferenceRegistration::Init(...) is used to initialize the parameters of the
+ registration algorithm. db_FrameToReferenceRegistration::AddFrame(...) is the method by which each new video frame
+ is introduced to the registration algorithm, and produces the estimated registration warp parameters.
+
+ The following example illustrates how the major methods of the class db_FrameToReferenceRegistration can be used together
+ to calculate the registration parameters for an image sequence.  In the example, the calls to the methods of
+ db_FrameToReferenceRegistration match those found in the API, but supporting code should be considered pseudo-code.
+ For a more complete example, please consult the source code for dbregtest.
+
+
+    \code
+    // feature-based image registration class:
+    db_FrameToReferenceRegistration reg;
+
+    // Image data
+    const unsigned char * const * image_storage;
+
+    // The 3x3 frame to reference registration parameters
+    double frame_to_ref_homography[9];
+
+    // a counter to count the number of frames processed.
+    unsigned long frame_counter;
+    // ...
+
+    // main loop - keep going while there are images to process.
+    while (ImagesAreAvailable)
+    {
+        // Call functions to place latest data into image_storage
+        // ...
+
+        // if the registration object is not yet initialized, then do so
+        // The arguments to this function are explained in the accompanying
+        // html API documentation
+        if (!reg.Initialized())
+        {
+            reg.Init(w,h,motion_model_type,25,linear_polish,quarter_resolution,
+                   DB_POINT_STANDARDDEV,reference_update_period,
+                   do_motion_smoothing,motion_smoothing_gain,
+                   DB_DEFAULT_NR_SAMPLES,DB_DEFAULT_CHUNK_SIZE,
+                   nr_corners,max_disparity);
+        }
+
+        // Present the new image data to the registration algorithm,
+        // with the result being stored in the frame_to_ref_homography
+        // variable.
+        reg.AddFrame(image_storage,frame_to_ref_homography);
+
+        // frame_to_ref_homography now contains the stabilizing transform
+        // use this to warp the latest image for display, etc.
+
+        // if this is the first frame, we need to tell the registration
+        // class to store the image as its reference.  Otherwise, AddFrame
+        // takes care of that.
+        if (frame_counter == 0)
+        {
+            reg.UpdateReference(image_storage);
+        }
+
+        // increment the frame counter
+        frame_counter++;
+    }
+
+    \endcode
+
+ */
+
+/*!
+ * Performs feature-based frame to reference image registration.
+ */
+class DBREG_API db_FrameToReferenceRegistration
+{
+public:
+    db_FrameToReferenceRegistration(void);
+    ~db_FrameToReferenceRegistration();
+
+    /*!
+     * Set parameters and allocate memory. Note: The default values of these parameters have been set to the values used for the android implementation (i.e. the demo APK).
+     * \param width         image width
+     * \param height        image height
+     * \param homography_type see definitions in \ref LMRobImageHomography
+     * \param max_iterations    max number of polishing steps
+     * \param linear_polish     whether to perform a linear polishing step after RANSAC
+     * \param quarter_resolution    whether to process input images at quarter resolution (for computational efficiency)
+     * \param scale         Cauchy scale coefficient (see db_ExpCauchyReprojectionError() )
+     * \param reference_update_period   how often to update the alignment reference (in units of number of frames)
+     * \param do_motion_smoothing   whether to perform display reference smoothing
+     * \param motion_smoothing_gain weight factor to reflect how fast the display reference must follow the current frame if motion smoothing is enabled
+     * \param nr_samples        number of times to compute a hypothesis
+     * \param chunk_size        size of cost chunks
+     * \param cd_target_nr_corners  target number of corners for corner detector
+     * \param cm_max_disparity      maximum disparity search range for corner matcher (in units of ratio of image width)
+     * \param cm_use_smaller_matching_window    if set to true, uses a correlation window of 5x5 instead of the default 11x11
+     * \param cd_nr_horz_blocks     the number of horizontal blocks for the corner detector to partition the image
+     * \param cd_nr_vert_blocks     the number of vertical blocks for the corner detector to partition the image
+    */
+    void Init(int width, int height,
+          int       homography_type = DB_HOMOGRAPHY_TYPE_DEFAULT,
+          int       max_iterations = DB_DEFAULT_MAX_ITERATIONS,
+          bool      linear_polish = false,
+          bool   quarter_resolution = true,
+          double  scale = DB_POINT_STANDARDDEV,
+          unsigned int reference_update_period = 3,
+          bool   do_motion_smoothing = false,
+          double motion_smoothing_gain = 0.75,
+          int   nr_samples = DB_DEFAULT_NR_SAMPLES,
+          int   chunk_size = DB_DEFAULT_CHUNK_SIZE,
+          int    cd_target_nr_corners = 500,
+          double cm_max_disparity = 0.2,
+          bool   cm_use_smaller_matching_window = false,
+          int    cd_nr_horz_blocks = 5,
+          int    cd_nr_vert_blocks = 5);
+
+    /*!
+     * Reset the transformation type that is being use to perform alignment. Use this to change the alignment type at run time.
+     * \param homography_type   the type of transformation to use for performing alignment (see definitions in \ref LMRobImageHomography)
+    */
+    void ResetHomographyType(int homography_type) { m_homography_type = homography_type; }
+
+    /*!
+     * Enable/Disable motion smoothing. Use this to turn motion smoothing on/off at run time.
+     * \param enable    flag indicating whether to turn the motion smoothing on or off.
+    */
+    void ResetSmoothing(bool enable) { m_do_motion_smoothing = enable; }
+
+    /*!
+     * Align an inspection image to an existing reference image, update the reference image if due and perform motion smoothing if enabled.
+     * \param im                new inspection image
+     * \param H             computed transformation from reference to inspection coordinate frame. Identity is returned if no reference frame was set.
+     * \param force_reference   make this the new reference image
+     */
+    int AddFrame(const unsigned char * const * im, double H[9], bool force_reference=false, bool prewarp=false);
+
+    /*!
+     * Returns true if Init() was run.
+     */
+    bool Initialized() const { return m_initialized; }
+
+    /*!
+     * Returns true if the current frame is being used as the alignment reference.
+    */
+    bool IsCurrentReference() const { return m_current_is_reference; }
+
+    /*!
+     * Returns true if we need to call UpdateReference now.
+     */
+    bool NeedReferenceUpdate();
+
+    /*!
+     * Returns the pointer reference to the alignment reference image data
+    */
+    unsigned char ** GetReferenceImage() { return m_reference_image; }
+
+    /*!
+     * Returns the pointer reference to the double array containing the homogeneous coordinates for the matched reference image corners.
+    */
+    double * GetRefCorners() { return m_corners_ref; }
+    /*!
+     * Returns the pointer reference to the double array containing the homogeneous coordinates for the matched inspection image corners.
+    */
+    double * GetInsCorners() { return m_corners_ins; }
+    /*!
+     * Returns the number of correspondences between the reference and inspection images.
+    */
+    int GetNrMatches() { return m_nr_matches; }
+
+    /*!
+     * Returns the number of corners detected in the current reference image.
+    */
+    int GetNrRefCorners() { return m_nr_corners_ref; }
+
+    /*!
+     * Returns the pointer to an array of indices that were found to be RANSAC inliers from the matched corner lists.
+    */
+    int* GetInliers() { return m_inlier_indices; }
+
+    /*!
+     * Returns the number of inliers from the RANSAC matching step.
+    */
+    int  GetNrInliers() { return m_num_inlier_indices; }
+
+    //std::vector<int>& GetInliers();
+    //void Polish(std::vector<int> &inlier_indices);
+
+    /*!
+     * Perform a linear polishing step by re-estimating the alignment transformation using the RANSAC inliers.
+     * \param inlier_indices    pointer to an array of indices that were found to be RANSAC inliers from the matched corner lists.
+     * \param num_inlier_indices    number of inliers i.e. the length of the array passed as the first argument.
+    */
+    void Polish(int *inlier_indices, int &num_inlier_indices);
+
+    /*!
+     * Reset the motion smoothing parameters to their initial values.
+    */
+    void ResetMotionSmoothingParameters() { m_stab_smoother.Init(); }
+
+    /*!
+     * Update the alignment reference image to the specified image.
+     * \param im    pointer to the image data to be used as the new alignment reference.
+     * \param subsample boolean flag to control whether the function should internally subsample the provided image to the size provided in the Init() function.
+    */
+    int UpdateReference(const unsigned char * const * im, bool subsample = true, bool detect_corners = true);
+
+    /*!
+     * Returns the transformation from the display reference to the alignment reference frame
+    */
+    void Get_H_dref_to_ref(double H[9]);
+    /*!
+     * Returns the transformation from the display reference to the inspection reference frame
+    */
+    void Get_H_dref_to_ins(double H[9]);
+    /*!
+     * Set the transformation from the display reference to the inspection reference frame
+     * \param H the transformation to set
+    */
+    void Set_H_dref_to_ins(double H[9]);
+
+    /*!
+     * Reset the display reference to the current frame.
+    */
+    void ResetDisplayReference();
+
+    /*!
+     * Estimate a secondary motion model starting from the specified transformation.
+     * \param H the primary motion model to start from
+    */
+    void EstimateSecondaryModel(double H[9]);
+
+    /*!
+     *
+    */
+    void SelectOutliers();
+
+    char *profile_string;
+
+protected:
+    void Clean();
+    void GenerateQuarterResImage(const unsigned char* const * im);
+
+    int     m_im_width;
+    int     m_im_height;
+
+    // RANSAC and refinement parameters:
+    int m_homography_type;
+    int     m_max_iterations;
+    double  m_scale;
+    int     m_nr_samples;
+    int     m_chunk_size;
+    double  m_outlier_t2;
+
+    // Whether to fit a linear model to just the inliers at the end
+    bool   m_linear_polish;
+    double m_polish_C[36];
+    double m_polish_D[6];
+
+    // local state
+    bool m_current_is_reference;
+    bool m_initialized;
+
+    // inspection to reference homography:
+    double m_H_ref_to_ins[9];
+    double m_H_dref_to_ref[9];
+
+    // feature extraction and matching:
+    db_CornerDetector_u m_cd;
+    db_Matcher_u        m_cm;
+
+    // length of corner arrays:
+    unsigned long m_max_nr_corners;
+
+    // corner locations of reference image features:
+    double * m_x_corners_ref;
+    double * m_y_corners_ref;
+    int  m_nr_corners_ref;
+
+    // corner locations of inspection image features:
+    double * m_x_corners_ins;
+    double * m_y_corners_ins;
+    int      m_nr_corners_ins;
+
+    // length of match index arrays:
+    unsigned long m_max_nr_matches;
+
+    // match indices:
+    int * m_match_index_ref;
+    int * m_match_index_ins;
+    int   m_nr_matches;
+
+    // pointer to internal copy of the reference image:
+    unsigned char ** m_reference_image;
+
+    // pointer to internal copy of last aligned inspection image:
+    unsigned char ** m_aligned_ins_image;
+
+    // pointer to quarter resolution image, if used.
+    unsigned char** m_quarter_res_image;
+
+    // temporary storage for the quarter resolution image processing
+    unsigned char** m_horz_smooth_subsample_image;
+
+    // temporary space for homography computation:
+    double * m_temp_double;
+    int * m_temp_int;
+
+    // homogenous image point arrays:
+    double * m_corners_ref;
+    double * m_corners_ins;
+
+    // Indices of the points within the match lists
+    int * m_inlier_indices;
+    int m_num_inlier_indices;
+
+    //void ComputeInliers(double H[9], std::vector<int> &inlier_indices);
+    void ComputeInliers(double H[9]);
+
+    // cost arrays:
+    void ComputeCostArray();
+    bool m_sq_cost_computed;
+    double * m_sq_cost;
+
+    // cost histogram:
+    void ComputeCostHistogram();
+    int *m_cost_histogram;
+
+    void SetOutlierThreshold();
+
+    // utility function for smoothing the motion parameters.
+    void SmoothMotion(void);
+
+private:
+    double m_K[9];
+    const int m_over_allocation;
+
+    bool m_reference_set;
+
+    // Maximum number of inliers seen until now w.r.t the current reference frame
+    int m_max_inlier_count;
+
+    // Number of cost histogram bins:
+    int m_nr_bins;
+    // All costs above this threshold get put into the last bin:
+    int m_max_cost_pix;
+
+    // whether to quarter the image resolution for processing, or not
+    bool m_quarter_resolution;
+
+    // the period (in number of frames) for reference update.
+    unsigned int m_reference_update_period;
+
+    // the number of frames processed so far.
+    unsigned int m_nr_frames_processed;
+
+    // smoother for motion transformations
+    db_StabilizationSmoother m_stab_smoother;
+
+    // boolean to control whether motion smoothing occurs (or not)
+    bool m_do_motion_smoothing;
+
+    // double to set the gain for motion smoothing
+    double m_motion_smoothing_gain;
+};
+/*!
+ Create look-up tables to undistort images. Only Bougeut (Matlab toolkit)
+ is currently supported. Can be used with db_WarpImageLut_u().
+ \code
+    xd = H*xs;
+    xd = xd/xd(3);
+ \endcode
+ \param lut_x   pre-allocated float image
+ \param lut_y   pre-allocated float image
+ \param w       width
+ \param h       height
+ \param H       image homography from source to destination
+ */
+inline void db_GenerateHomographyLut(float ** lut_x,float ** lut_y,int w,int h,const double H[9])
+{
+    assert(lut_x && lut_y);
+    double x[3] = {0.0,0.0,1.0};
+    double xb[3];
+
+/*
+    double xl[3];
+
+    // Determine the output coordinate system ROI
+    double Hinv[9];
+    db_InvertAffineTransform(Hinv,H);
+    db_Multiply3x3_3x1(xl, Hinv, x);
+    xl[0] = db_SafeDivision(xl[0],xl[2]);
+    xl[1] = db_SafeDivision(xl[1],xl[2]);
+*/
+
+    for ( int i = 0; i < w; ++i )
+        for ( int j = 0; j < h; ++j )
+        {
+            x[0] = double(i);
+            x[1] = double(j);
+            db_Multiply3x3_3x1(xb, H, x);
+            xb[0] = db_SafeDivision(xb[0],xb[2]);
+            xb[1] = db_SafeDivision(xb[1],xb[2]);
+
+            lut_x[j][i] = float(xb[0]);
+            lut_y[j][i] = float(xb[1]);
+        }
+}
+
+/*!
+ * Perform a look-up table warp for packed RGB ([rgbrgbrgb...]) images.
+ * The LUTs must be float images of the same size as source image.
+ * The source value x_s is determined from destination (x_d,y_d) through lut_x
+ * and y_s is determined from lut_y:
+   \code
+   x_s = lut_x[y_d][x_d];
+   y_s = lut_y[y_d][x_d];
+   \endcode
+
+ * \param src   source image (w*3 by h)
+ * \param dst   destination image (w*3 by h)
+ * \param w     width
+ * \param h     height
+ * \param lut_x LUT for x
+ * \param lut_y LUT for y
+ */
+inline void db_WarpImageLutFast_rgb(const unsigned char * const * src, unsigned char ** dst, int w, int h,
+                                  const float * const * lut_x, const float * const * lut_y)
+{
+    assert(src && dst);
+    int xd=0, yd=0;
+
+    for ( int i = 0; i < w; ++i )
+        for ( int j = 0; j < h; ++j )
+        {
+            xd = static_cast<unsigned int>(lut_x[j][i]);
+            yd = static_cast<unsigned int>(lut_y[j][i]);
+            if ( xd >= w || yd >= h ||
+                 xd < 0 || yd < 0)
+            {
+                dst[j][3*i  ] = 0;
+                dst[j][3*i+1] = 0;
+                dst[j][3*i+2] = 0;
+            }
+            else
+            {
+                dst[j][3*i  ] = src[yd][3*xd  ];
+                dst[j][3*i+1] = src[yd][3*xd+1];
+                dst[j][3*i+2] = src[yd][3*xd+2];
+            }
+        }
+}
+
+inline unsigned char db_BilinearInterpolationRGB(double y, double x, const unsigned char * const * v, int offset)
+{
+         int floor_x=(int) x;
+         int floor_y=(int) y;
+
+         int ceil_x=floor_x+1;
+         int ceil_y=floor_y+1;
+
+         unsigned char f00 = v[floor_y][3*floor_x+offset];
+         unsigned char f01 = v[floor_y][3*ceil_x+offset];
+         unsigned char f10 = v[ceil_y][3*floor_x+offset];
+         unsigned char f11 = v[ceil_y][3*ceil_x+offset];
+
+         double xl = x-floor_x;
+         double yl = y-floor_y;
+
+         return (unsigned char)(f00*(1-yl)*(1-xl) + f10*yl*(1-xl) + f01*(1-yl)*xl + f11*yl*xl);
+}
+
+inline void db_WarpImageLutBilinear_rgb(const unsigned char * const * src, unsigned char ** dst, int w, int h,
+                                  const float * const * lut_x, const float * const * lut_y)
+{
+    assert(src && dst);
+    double xd=0.0, yd=0.0;
+
+    for ( int i = 0; i < w; ++i )
+        for ( int j = 0; j < h; ++j )
+        {
+            xd = static_cast<double>(lut_x[j][i]);
+            yd = static_cast<double>(lut_y[j][i]);
+            if ( xd > w-2 || yd > h-2 ||
+                 xd < 0.0 || yd < 0.0)
+            {
+                dst[j][3*i  ] = 0;
+                dst[j][3*i+1] = 0;
+                dst[j][3*i+2] = 0;
+            }
+            else
+            {
+                dst[j][3*i  ] = db_BilinearInterpolationRGB(yd,xd,src,0);
+                dst[j][3*i+1] = db_BilinearInterpolationRGB(yd,xd,src,1);
+                dst[j][3*i+2] = db_BilinearInterpolationRGB(yd,xd,src,2);
+            }
+        }
+}
+
+inline double SquaredInhomogenousHomographyError(double y[3],double H[9],double x[3]){
+    double x0,x1,x2,mult;
+    double sd;
+
+    x0=H[0]*x[0]+H[1]*x[1]+H[2];
+    x1=H[3]*x[0]+H[4]*x[1]+H[5];
+    x2=H[6]*x[0]+H[7]*x[1]+H[8];
+    mult=1.0/((x2!=0.0)?x2:1.0);
+    sd=(y[0]-x0*mult)*(y[0]-x0*mult)+(y[1]-x1*mult)*(y[1]-x1*mult);
+
+    return(sd);
+}
+
+
+// functions related to profiling
+#if PROFILE
+
+/* return current time in milliseconds */
+static double
+now_ms(void)
+{
+    //struct timespec res;
+    struct timeval res;
+    //clock_gettime(CLOCK_REALTIME, &res);
+    gettimeofday(&res, NULL);
+    return 1000.0*res.tv_sec + (double)res.tv_usec/1e3;
+}
+
+#endif
diff --git a/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.cpp b/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.cpp
new file mode 100644
index 0000000..dffff8a
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.cpp
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <stdlib.h>
+#include "dbstabsmooth.h"
+
+///// TODO TODO ////////// Replace this with the actual definition from Jayan's reply /////////////
+#define vp_copy_motion_no_id vp_copy_motion
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool vpmotion_add(VP_MOTION *in1, VP_MOTION *in2, VP_MOTION *out);
+static bool vpmotion_multiply(VP_MOTION *in1, double factor, VP_MOTION *out);
+
+db_StabilizationSmoother::db_StabilizationSmoother()
+{
+    Init();
+}
+
+void db_StabilizationSmoother::Init()
+{
+    f_smoothOn = true;
+    f_smoothReset = false;
+    f_smoothFactor = 1.0f;
+    f_minDampingFactor = 0.2f;
+    f_zoom = 1.0f;
+    VP_MOTION_ID(f_motLF);
+    VP_MOTION_ID(f_imotLF);
+    f_hsize = 0;
+    f_vsize = 0;
+
+    VP_MOTION_ID(f_disp_mot);
+    VP_MOTION_ID(f_src_mot);
+    VP_MOTION_ID(f_diff_avg);
+
+    for( int i = 0; i < MOTION_ARRAY-1; i++) {
+        VP_MOTION_ID(f_hist_mot_speed[i]);
+        VP_MOTION_ID(f_hist_mot[i]);
+        VP_MOTION_ID(f_hist_diff_mot[i]);
+    }
+    VP_MOTION_ID(f_hist_mot[MOTION_ARRAY-1]);
+
+}
+
+db_StabilizationSmoother::~db_StabilizationSmoother()
+{}
+
+
+bool db_StabilizationSmoother::smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot)
+{
+    VP_MOTION_ID(f_motLF);
+    VP_MOTION_ID(f_imotLF);
+    f_motLF.insid = inmot->refid;
+    f_motLF.refid = inmot->insid;
+
+    if(f_smoothOn) {
+        if(!f_smoothReset) {
+            MXX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXX(f_motLF) + (1.0-f_smoothFactor)* (double) MXX(*inmot));
+            MXY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXY(f_motLF) + (1.0-f_smoothFactor)* (double) MXY(*inmot));
+            MXZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXZ(f_motLF) + (1.0-f_smoothFactor)* (double) MXZ(*inmot));
+            MXW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MXW(f_motLF) + (1.0-f_smoothFactor)* (double) MXW(*inmot));
+
+            MYX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYX(f_motLF) + (1.0-f_smoothFactor)* (double) MYX(*inmot));
+            MYY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYY(f_motLF) + (1.0-f_smoothFactor)* (double) MYY(*inmot));
+            MYZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYZ(f_motLF) + (1.0-f_smoothFactor)* (double) MYZ(*inmot));
+            MYW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MYW(f_motLF) + (1.0-f_smoothFactor)* (double) MYW(*inmot));
+
+            MZX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZX(f_motLF) + (1.0-f_smoothFactor)* (double) MZX(*inmot));
+            MZY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZY(f_motLF) + (1.0-f_smoothFactor)* (double) MZY(*inmot));
+            MZZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZZ(f_motLF) + (1.0-f_smoothFactor)* (double) MZZ(*inmot));
+            MZW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MZW(f_motLF) + (1.0-f_smoothFactor)* (double) MZW(*inmot));
+
+            MWX(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWX(f_motLF) + (1.0-f_smoothFactor)* (double) MWX(*inmot));
+            MWY(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWY(f_motLF) + (1.0-f_smoothFactor)* (double) MWY(*inmot));
+            MWZ(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWZ(f_motLF) + (1.0-f_smoothFactor)* (double) MWZ(*inmot));
+            MWW(f_motLF) = (VP_PAR) (f_smoothFactor*(double) MWW(f_motLF) + (1.0-f_smoothFactor)* (double) MWW(*inmot));
+        }
+        else
+            vp_copy_motion_no_id(inmot, &f_motLF); // f_smoothFactor = 0.0
+
+        // Only allow LF motion to be compensated. Remove HF motion from
+        // the output transformation
+        if(!vp_invert_motion(&f_motLF, &f_imotLF))
+            return false;
+
+        if(!vp_cascade_motion(&f_imotLF, inmot, outmot))
+            return false;
+    }
+    else {
+        vp_copy_motion_no_id(inmot, outmot);
+    }
+
+    return true;
+}
+
+bool db_StabilizationSmoother::smoothMotionAdaptive(/*VP_BIMG *bimg,*/int hsize, int vsize, VP_MOTION *inmot, VP_MOTION *outmot)
+{
+    VP_MOTION tmpMotion, testMotion;
+    VP_PAR p1x, p2x, p3x, p4x;
+    VP_PAR p1y, p2y, p3y, p4y;
+    double smoothFactor;
+    double minSmoothFactor = f_minDampingFactor;
+
+//  int hsize = bimg->w;
+//  int vsize = bimg->h;
+    double border_factor = 0.01;//0.2;
+    double border_x = border_factor * hsize;
+    double border_y = border_factor * vsize;
+
+    VP_MOTION_ID(f_motLF);
+    VP_MOTION_ID(f_imotLF);
+    VP_MOTION_ID(testMotion);
+    VP_MOTION_ID(tmpMotion);
+
+    if (f_smoothOn) {
+        VP_MOTION identityMotion;
+        VP_MOTION_ID(identityMotion); // initialize the motion
+        vp_copy_motion(inmot/*in*/, &testMotion/*out*/);
+        VP_PAR delta = vp_motion_cornerdiff(&testMotion, &identityMotion, 0, 0,(int)hsize, (int)vsize);
+
+        smoothFactor = 0.99 - 0.0015 * delta;
+
+        if(smoothFactor < minSmoothFactor)
+            smoothFactor = minSmoothFactor;
+
+        // Find the amount of motion that must be compensated so that no "border" pixels are seen in the stable video
+        for (smoothFactor = smoothFactor; smoothFactor >= minSmoothFactor; smoothFactor -= 0.01) {
+            // Compute the smoothed motion
+            if(!smoothMotion(inmot, &tmpMotion, smoothFactor))
+                break;
+
+            // TmpMotion, or Qsi where s is the smoothed display reference and i is the
+            // current image, tells us how points in the S co-ordinate system map to
+            // points in the I CS.  We would like to check whether the four corners of the
+            // warped and smoothed display reference lies entirely within the I co-ordinate
+            // system.  If yes, then the amount of smoothing is sufficient so that NO
+            // border pixels are seen at the output.  We test for f_smoothFactor terms
+            // between 0.9 and 1.0, in steps of 0.01, and between 0.5 ands 0.9 in steps of 0.1
+
+            (void) vp_zoom_motion2d(&tmpMotion, &testMotion, 1, hsize, vsize, (double)f_zoom); // needs to return bool
+
+            VP_WARP_POINT_2D(0, 0, testMotion, p1x, p1y);
+            VP_WARP_POINT_2D(hsize - 1, 0, testMotion, p2x, p2y);
+            VP_WARP_POINT_2D(hsize - 1, vsize - 1, testMotion, p3x, p3y);
+            VP_WARP_POINT_2D(0, vsize - 1, testMotion, p4x, p4y);
+
+            if (!is_point_in_rect((double)p1x,(double)p1y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
+                continue;
+            }
+            if (!is_point_in_rect((double)p2x, (double)p2y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
+                continue;
+            }
+            if (!is_point_in_rect((double)p3x,(double)p3y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
+                continue;
+            }
+            if (!is_point_in_rect((double)p4x, (double)p4y,-border_x,-border_y,(double)(hsize+2.0*border_x),(double)(vsize+2.0*border_y))) {
+                continue;
+            }
+
+            // If we get here, then all the points are in the rectangle.
+            // Therefore, break out of this loop
+            break;
+        }
+
+        // if we get here and f_smoothFactor <= fMinDampingFactor, reset the stab reference
+        if (smoothFactor < f_minDampingFactor)
+            smoothFactor = f_minDampingFactor;
+
+        // use the smoothed motion for stabilization
+        vp_copy_motion_no_id(&tmpMotion/*in*/, outmot/*out*/);
+    }
+    else
+    {
+        vp_copy_motion_no_id(inmot, outmot);
+    }
+
+    return true;
+}
+
+bool db_StabilizationSmoother::smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot, double smooth_factor)
+{
+    f_motLF.insid = inmot->refid;
+    f_motLF.refid = inmot->insid;
+
+    if(f_smoothOn) {
+        if(!f_smoothReset) {
+            MXX(f_motLF) = (VP_PAR) (smooth_factor*(double) MXX(f_motLF) + (1.0-smooth_factor)* (double) MXX(*inmot));
+            MXY(f_motLF) = (VP_PAR) (smooth_factor*(double) MXY(f_motLF) + (1.0-smooth_factor)* (double) MXY(*inmot));
+            MXZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MXZ(f_motLF) + (1.0-smooth_factor)* (double) MXZ(*inmot));
+            MXW(f_motLF) = (VP_PAR) (smooth_factor*(double) MXW(f_motLF) + (1.0-smooth_factor)* (double) MXW(*inmot));
+
+            MYX(f_motLF) = (VP_PAR) (smooth_factor*(double) MYX(f_motLF) + (1.0-smooth_factor)* (double) MYX(*inmot));
+            MYY(f_motLF) = (VP_PAR) (smooth_factor*(double) MYY(f_motLF) + (1.0-smooth_factor)* (double) MYY(*inmot));
+            MYZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MYZ(f_motLF) + (1.0-smooth_factor)* (double) MYZ(*inmot));
+            MYW(f_motLF) = (VP_PAR) (smooth_factor*(double) MYW(f_motLF) + (1.0-smooth_factor)* (double) MYW(*inmot));
+
+            MZX(f_motLF) = (VP_PAR) (smooth_factor*(double) MZX(f_motLF) + (1.0-smooth_factor)* (double) MZX(*inmot));
+            MZY(f_motLF) = (VP_PAR) (smooth_factor*(double) MZY(f_motLF) + (1.0-smooth_factor)* (double) MZY(*inmot));
+            MZZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MZZ(f_motLF) + (1.0-smooth_factor)* (double) MZZ(*inmot));
+            MZW(f_motLF) = (VP_PAR) (smooth_factor*(double) MZW(f_motLF) + (1.0-smooth_factor)* (double) MZW(*inmot));
+
+            MWX(f_motLF) = (VP_PAR) (smooth_factor*(double) MWX(f_motLF) + (1.0-smooth_factor)* (double) MWX(*inmot));
+            MWY(f_motLF) = (VP_PAR) (smooth_factor*(double) MWY(f_motLF) + (1.0-smooth_factor)* (double) MWY(*inmot));
+            MWZ(f_motLF) = (VP_PAR) (smooth_factor*(double) MWZ(f_motLF) + (1.0-smooth_factor)* (double) MWZ(*inmot));
+            MWW(f_motLF) = (VP_PAR) (smooth_factor*(double) MWW(f_motLF) + (1.0-smooth_factor)* (double) MWW(*inmot));
+        }
+        else
+            vp_copy_motion_no_id(inmot, &f_motLF); // smooth_factor = 0.0
+
+        // Only allow LF motion to be compensated. Remove HF motion from
+        // the output transformation
+        if(!vp_invert_motion(&f_motLF, &f_imotLF))
+            return false;
+
+        if(!vp_cascade_motion(&f_imotLF, inmot, outmot))
+            return false;
+    }
+    else {
+        vp_copy_motion_no_id(inmot, outmot);
+    }
+
+    return true;
+}
+
+//! Overloaded smoother function that takes in user-specidied smoothing factor
+bool
+db_StabilizationSmoother::smoothMotion1(VP_MOTION *inmot, VP_MOTION *outmot, VP_MOTION *motLF, VP_MOTION *imotLF, double factor)
+{
+
+    if(!f_smoothOn) {
+        vp_copy_motion(inmot, outmot);
+        return true;
+    }
+    else {
+        if(!f_smoothReset) {
+            MXX(*motLF) = (VP_PAR) (factor*(double) MXX(*motLF) + (1.0-factor)* (double) MXX(*inmot));
+            MXY(*motLF) = (VP_PAR) (factor*(double) MXY(*motLF) + (1.0-factor)* (double) MXY(*inmot));
+            MXZ(*motLF) = (VP_PAR) (factor*(double) MXZ(*motLF) + (1.0-factor)* (double) MXZ(*inmot));
+            MXW(*motLF) = (VP_PAR) (factor*(double) MXW(*motLF) + (1.0-factor)* (double) MXW(*inmot));
+
+            MYX(*motLF) = (VP_PAR) (factor*(double) MYX(*motLF) + (1.0-factor)* (double) MYX(*inmot));
+            MYY(*motLF) = (VP_PAR) (factor*(double) MYY(*motLF) + (1.0-factor)* (double) MYY(*inmot));
+            MYZ(*motLF) = (VP_PAR) (factor*(double) MYZ(*motLF) + (1.0-factor)* (double) MYZ(*inmot));
+            MYW(*motLF) = (VP_PAR) (factor*(double) MYW(*motLF) + (1.0-factor)* (double) MYW(*inmot));
+
+            MZX(*motLF) = (VP_PAR) (factor*(double) MZX(*motLF) + (1.0-factor)* (double) MZX(*inmot));
+            MZY(*motLF) = (VP_PAR) (factor*(double) MZY(*motLF) + (1.0-factor)* (double) MZY(*inmot));
+            MZZ(*motLF) = (VP_PAR) (factor*(double) MZZ(*motLF) + (1.0-factor)* (double) MZZ(*inmot));
+            MZW(*motLF) = (VP_PAR) (factor*(double) MZW(*motLF) + (1.0-factor)* (double) MZW(*inmot));
+
+            MWX(*motLF) = (VP_PAR) (factor*(double) MWX(*motLF) + (1.0-factor)* (double) MWX(*inmot));
+            MWY(*motLF) = (VP_PAR) (factor*(double) MWY(*motLF) + (1.0-factor)* (double) MWY(*inmot));
+            MWZ(*motLF) = (VP_PAR) (factor*(double) MWZ(*motLF) + (1.0-factor)* (double) MWZ(*inmot));
+            MWW(*motLF) = (VP_PAR) (factor*(double) MWW(*motLF) + (1.0-factor)* (double) MWW(*inmot));
+        }
+        else {
+            vp_copy_motion(inmot, motLF);
+        }
+        // Only allow LF motion to be compensated. Remove HF motion from the output transformation
+        if(!vp_invert_motion(motLF, imotLF)) {
+#if DEBUG_PRINT
+            printfOS("Invert failed \n");
+#endif
+            return false;
+        }
+        if(!vp_cascade_motion(imotLF, inmot, outmot)) {
+#if DEBUG_PRINT
+            printfOS("cascade failed \n");
+#endif
+            return false;
+        }
+    }
+    return true;
+}
+
+
+
+
+bool db_StabilizationSmoother::is_point_in_rect(double px, double py, double rx, double ry, double w, double h)
+{
+    if (px < rx)
+        return(false);
+    if (px >= rx + w)
+        return(false);
+    if (py < ry)
+        return(false);
+    if (py >= ry + h)
+        return(false);
+
+    return(true);
+}
+
+
+
+static bool vpmotion_add(VP_MOTION *in1, VP_MOTION *in2, VP_MOTION *out)
+{
+    int i;
+    if(in1 == NULL || in2 == NULL || out == NULL)
+        return false;
+
+    for(i = 0; i < VP_MAX_MOTION_PAR; i++)
+        out->par[i] = in1->par[i] + in2->par[i];
+
+    return true;
+}
+
+static bool vpmotion_multiply(VP_MOTION *in1, double factor, VP_MOTION *out)
+{
+    int i;
+    if(in1 == NULL || out == NULL)
+        return false;
+
+    for(i = 0; i < VP_MAX_MOTION_PAR; i++)
+        out->par[i] = in1->par[i] * factor;
+
+    return true;
+}
+
diff --git a/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.h b/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.h
new file mode 100644
index 0000000..f03546e
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/dbstabsmooth.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#pragma once
+
+
+#ifdef _WIN32
+#ifdef DBREG_EXPORTS
+#define DBREG_API __declspec(dllexport)
+#else
+#define DBREG_API __declspec(dllimport)
+#endif
+#else
+#define DBREG_API
+#endif
+
+extern "C" {
+#include "vp_motionmodel.h"
+}
+
+#define MOTION_ARRAY 5
+
+
+/*!
+ * Performs smoothing on the motion estimate from feature_stab.
+ */
+class DBREG_API db_StabilizationSmoother
+{
+public:
+    db_StabilizationSmoother();
+    ~db_StabilizationSmoother();
+
+    /*!
+     * Initialize parameters for stab-smoother.
+    */
+    void Init();
+
+    //! Smothing type
+    typedef enum {
+        SimpleSmooth = 0, //!< simple smooth
+        AdaptSmooth  = 1, //!< adaptive smooth
+        PanSmooth    = 2  //!< pan motion smooth
+    } SmoothType;
+
+    /*!
+     * Smooth-motion is to do a weight-average between the current affine and
+     * motLF. The way to change the affine is only for the display purpose.
+     * It removes the high frequency motion and keep the low frequency motion
+     * to the display. IIR implmentation.
+     * \param inmot input motion parameters
+     * \param outmot smoothed output motion parameters
+    */
+    bool smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot);
+
+    /*!
+     * The adaptive smoothing version of the above fixed smoothing function.
+     * \param hsize width of the image being aligned
+     * \param vsize height of the image being aligned
+     * \param inmot input motion parameters
+     * \param outmot    smoothed output motion parameters
+    */
+    bool smoothMotionAdaptive(/*VP_BIMG *bimg,*/int hsize, int vsize, VP_MOTION *inmot, VP_MOTION *outmot);
+    bool smoothPanMotion_1(VP_MOTION *inmot, VP_MOTION *outmot);
+    bool smoothPanMotion_2(VP_MOTION *inmot, VP_MOTION *outmot);
+
+    /*!
+    * Set the smoothing factor for the stab-smoother.
+    * \param factor the factor value to set
+    */
+    inline void setSmoothingFactor(float factor) { f_smoothFactor = factor; }
+
+    /*!
+     * Reset smoothing
+    */
+    inline void resetSmoothing(bool flag) { f_smoothReset = flag; }
+    /*!
+     * Set the zoom factor value.
+     * \param zoom  the value to set to
+    */
+    inline void setZoomFactor(float zoom) { f_zoom = zoom; }
+    /*!
+     * Set the minimum damping factor value.
+     * \param factor    the value to set to
+    */
+    inline void setminDampingFactor(float factor) { f_minDampingFactor = factor; }
+
+    /*!
+     * Returns the current smoothing factor.
+    */
+    inline float getSmoothingFactor(void) { return f_smoothFactor; }
+    /*!
+     * Returns the current zoom factor.
+    */
+    inline float getZoomFactor(void) { return f_zoom; }
+    /*!
+     * Returns the current minimum damping factor.
+    */
+    inline float getminDampingFactor(void) { return f_minDampingFactor; }
+    /*!
+     * Returns the current state of the smoothing reset flag.
+    */
+    inline bool  getSmoothReset(void) { return f_smoothReset; }
+    /*!
+     * Returns the current low frequency motion parameters.
+    */
+    inline VP_MOTION getMotLF(void) { return f_motLF; }
+    /*!
+     * Returns the inverse of the current low frequency motion parameters.
+    */
+    inline VP_MOTION getImotLF(void) { return f_imotLF; }
+    /*!
+     * Set the dimensions of the alignment image.
+     * \param hsize width of the image
+     * \param vsize height of the image
+    */
+    inline void setSize(int hsize, int vsize) { f_hsize = hsize; f_vsize = vsize; }
+
+protected:
+
+    bool smoothMotion(VP_MOTION *inmot, VP_MOTION *outmot, double smooth_factor);
+    bool smoothMotion1(VP_MOTION *inmot, VP_MOTION *outmot, VP_MOTION *motLF, VP_MOTION *imotLF, double smooth_factor);
+    void iterativeSmooth(VP_MOTION *input, VP_MOTION *output, double border_factor);
+    bool is_point_in_rect(double px, double py, double rx, double ry, double w, double h);
+
+
+private:
+    int f_hsize;
+    int f_vsize;
+    bool f_smoothOn;
+    bool f_smoothReset;
+    float f_smoothFactor;
+    float f_minDampingFactor;
+    float f_zoom;
+    VP_MOTION f_motLF;
+    VP_MOTION f_imotLF;
+    VP_MOTION f_hist_mot[MOTION_ARRAY];
+    VP_MOTION f_hist_mot_speed[MOTION_ARRAY-1];
+    VP_MOTION f_hist_diff_mot[MOTION_ARRAY-1];
+    VP_MOTION f_disp_mot;
+    VP_MOTION f_src_mot;
+    VP_MOTION f_diff_avg;
+
+};
+
diff --git a/jni_mosaic/feature_stab/src/dbreg/targetver.h b/jni_mosaic/feature_stab/src/dbreg/targetver.h
new file mode 100644
index 0000000..3ca3e87
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/targetver.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#pragma once
+
+// The following macros define the minimum required platform.  The minimum required platform
+// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run
+// your application.  The macros work by enabling all features available on platform versions up to and
+// including the version specified.
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef WINVER                          // Specifies that the minimum required platform is Windows Vista.
+#define WINVER 0x0600           // Change this to the appropriate value to target other versions of Windows.
+#endif
+
+#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
+#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
+#endif
+
+#ifndef _WIN32_WINDOWS          // Specifies that the minimum required platform is Windows 98.
+#define _WIN32_WINDOWS 0x0410 // Change this to the appropriate value to target Windows Me or later.
+#endif
+
+#ifndef _WIN32_IE                       // Specifies that the minimum required platform is Internet Explorer 7.0.
+#define _WIN32_IE 0x0700        // Change this to the appropriate value to target other versions of IE.
+#endif
diff --git a/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.c b/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.c
new file mode 100644
index 0000000..1f6af15
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.c
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+#sourcefile  vpmotion/vp_motionmodel.c
+#category    motion-model
+*
+* Copyright 1998 Sarnoff Corporation
+* All Rights Reserved
+*
+* Modification History
+*      Date: 02/14/98
+*      Author: supuns
+*      Shop Order: 17xxx
+*              @(#) $Id: vp_motionmodel.c,v 1.4 2011/06/17 14:04:33 mbansal Exp $
+*/
+
+/*
+* ===================================================================
+* Include Files
+*/
+
+#include <string.h> /* memmove */
+#include <math.h>
+#include "vp_motionmodel.h"
+
+/* Static Functions */
+static
+double Det3(double m[3][3])
+{
+  double result;
+
+  result =
+    m[0][0]*m[1][1]*m[2][2] + m[0][1]*m[1][2]*m[2][0] +
+    m[0][2]*m[1][0]*m[2][1] - m[0][2]*m[1][1]*m[2][0] -
+    m[0][0]*m[1][2]*m[2][1] - m[0][1]*m[1][0]*m[2][2];
+
+  return(result);
+}
+
+typedef double MATRIX[4][4];
+
+static
+double Det4(MATRIX m)
+{
+    /* ==> This is a poor implementation of determinant.
+       Writing the formula out in closed form is unnecessarily complicated
+       and mistakes are easy to make. */
+  double result;
+
+  result=
+    m[0][3] *m[1][2] *m[2][1] *m[3][0] - m[0][2] *m[1][3] *m[2][1] *m[3][0] - m[0][3] *m[1][1] *m[2][2] *m[3][0] +
+    m[0][1] *m[1][3] *m[2][2] *m[3][0] + m[0][2] *m[1][1] *m[2][3] *m[3][0] - m[0][1] *m[1][2] *m[2][3] *m[3][0] - m[0][3] *m[1][2] *m[2][0] *m[3][1] +
+    m[0][2] *m[1][3] *m[2][0] *m[3][1] + m[0][3] *m[1][0] *m[2][2] *m[3][1] - m[0][0] *m[1][3] *m[2][2] *m[3][1] - m[0][2] *m[1][0] *m[2][3] *m[3][1] +
+    m[0][0] *m[1][2] *m[2][3] *m[3][1] + m[0][3] *m[1][1] *m[2][0] *m[3][2] - m[0][1] *m[1][3] *m[2][0] *m[3][2] - m[0][3] *m[1][0] *m[2][1] *m[3][2] +
+    m[0][0] *m[1][3] *m[2][1] *m[3][2] + m[0][1] *m[1][0] *m[2][3] *m[3][2] - m[0][0] *m[1][1] *m[2][3] *m[3][2] - m[0][2] *m[1][1] *m[2][0] *m[3][3] +
+    m[0][1] *m[1][2] *m[2][0] *m[3][3] + m[0][2] *m[1][0] *m[2][1] *m[3][3] - m[0][0] *m[1][2] *m[2][1] *m[3][3] - m[0][1] *m[1][0] *m[2][2] *m[3][3] +
+    m[0][0] *m[1][1] *m[2][2] *m[3][3];
+  /*
+    m[0][0]*m[1][1]*m[2][2]*m[3][3]-m[0][1]*m[1][0]*m[2][2]*m[3][3]+
+    m[0][1]*m[1][2]*m[2][0]*m[3][3]-m[0][2]*m[1][1]*m[2][0]*m[3][3]+
+    m[0][2]*m[1][0]*m[2][1]*m[3][3]-m[0][0]*m[1][2]*m[2][1]*m[3][3]+
+    m[0][0]*m[1][2]*m[2][3]*m[3][1]-m[0][2]*m[1][0]*m[2][3]*m[3][1]+
+    m[0][2]*m[1][3]*m[2][0]*m[3][1]-m[0][3]*m[1][2]*m[2][0]*m[3][1]+
+    m[0][3]*m[1][0]*m[2][2]*m[3][1]-m[0][0]*m[1][3]*m[2][2]*m[3][1]+
+    m[0][0]*m[1][3]*m[2][1]*m[3][2]-m[0][3]*m[1][0]*m[2][3]*m[3][2]+
+    m[0][1]*m[1][0]*m[2][3]*m[3][2]-m[0][0]*m[1][1]*m[2][0]*m[3][2]+
+    m[0][3]*m[1][1]*m[2][0]*m[3][2]-m[0][1]*m[1][3]*m[2][1]*m[3][2]+
+    m[0][1]*m[1][3]*m[2][2]*m[3][0]-m[0][3]*m[1][1]*m[2][2]*m[3][0]+
+    m[0][2]*m[1][1]*m[2][3]*m[3][0]-m[0][1]*m[1][2]*m[2][3]*m[3][0]+
+    m[0][3]*m[1][2]*m[2][1]*m[3][0]-m[0][2]*m[1][3]*m[2][1]*m[3][0];
+    */
+  return(result);
+}
+
+static
+int inv4Mat(const VP_MOTION* in, VP_MOTION* out)
+{
+    /* ==> This is a poor implementation of inversion.  The determinant
+       method is O(N^4), i.e. unnecessarily slow, and not numerically accurate.
+       The real complexity of inversion is O(N^3), and is best done using
+       LU decomposition. */
+
+  MATRIX inmat,outmat;
+  int i, j, k, l, m, n,ntemp;
+  double mat[3][3], indet, temp;
+
+  /* check for non-empty structures structure */
+  if (((VP_MOTION *) NULL == in) || ((VP_MOTION *) NULL == out)) {
+    return 1;
+  }
+
+  for(k=0,i=0;i<4;i++)
+    for(j=0;j<4;j++,k++)
+      inmat[i][j]=(double)in->par[k];
+
+  indet = Det4(inmat);
+  if (indet==0) return(-1);
+
+  for (i=0;i<4;i++) {
+    for (j=0;j<4;j++) {
+      m = 0;
+      for (k=0;k<4;k++) {
+    if (i != k) {
+      n = 0;
+      for (l=0;l<4;l++)
+        if (j != l) {
+          mat[m][n] = inmat[k][l];
+          n++;
+        }
+      m++;
+    }
+      }
+
+      temp = -1.;
+      ntemp = (i +j ) %2;
+      if( ntemp == 0)  temp = 1.;
+
+      outmat[j][i] = temp * Det3(mat)/indet;
+    }
+  }
+
+  for(k=0,i=0;i<4;i++)
+    for(j=0;j<4;j++,k++)
+      out->par[k]=(VP_PAR)outmat[i][j]; /*lint !e771*/
+
+  return(0);
+}
+
+/*
+* ===================================================================
+* Public Functions
+#htmlstart
+*/
+
+/*
+ * ===================================================================
+#fn vp_invert_motion
+#ft invert a motion
+#fd DEFINITION
+       Bool
+       vp_invert_motion(const VP_MOTION* in,VP_MOTION* out)
+#fd PURPOSE
+       This inverts the motion given in 'in'.
+       All motion models upto VP_MOTION_SEMI_PROJ_3D are supported.
+       It is assumed that the all 16 parameters are properly
+       initialized although you may not be using them. You could
+       use the VP_KEEP_ macro's defined in vp_motionmodel.h to set
+       the un-initialized parameters. This uses a 4x4 matrix invertion
+       function internally.
+       It is SAFE to pass the same pointer as both the 'in' and 'out'
+       parameters.
+#fd INPUTS
+       in  - input motion
+#fd OUTPUTS
+       out - output inverted motion. If singular matrix uninitialized.
+             if MWW(in) is non-zero it is also normalized.
+#fd RETURNS
+       FALSE - matrix is singular or motion model not supported
+       TRUE  - otherwise
+#fd SIDE EFFECTS
+       None
+#endfn
+*/
+
+int vp_invert_motion(const VP_MOTION* in,VP_MOTION* out)
+{
+  int refid;
+
+  /* check for non-empty structures structure */
+  if (((VP_MOTION *) NULL == in) || ((VP_MOTION *) NULL == out)) {
+    return FALSE;
+  }
+
+  if (in->type>VP_MOTION_SEMI_PROJ_3D) {
+    return FALSE;
+  }
+
+  if (inv4Mat(in,out)<0)
+    return FALSE;
+
+  /*VP_NORMALIZE(*out);*/
+  out->type = in->type;
+  refid=in->refid;
+  out->refid=in->insid;
+  out->insid=refid;
+  return TRUE;
+}
+
+/*
+* ===================================================================
+#fn vp_cascade_motion
+#ft Cascade two motion transforms
+#fd DEFINITION
+      Bool
+      vp_cascade_motion(const VP_MOTION* InAB,const VP_MOTION* InBC,VP_MOTION* OutAC)
+#fd PURPOSE
+      Given Motion Transforms A->B and B->C, this function will
+      generate a New Motion that describes the transformation
+      from A->C.
+      More specifically, OutAC = InBC * InAC.
+      This function works ok if InAB,InBC and OutAC are the same pointer.
+#fd INPUTS
+      InAB - First Motion Transform
+      InBC - Second Motion Tranform
+#fd OUTPUTS
+      OutAC - Cascaded Motion
+#fd RETURNS
+      FALSE - motion model not supported
+      TRUE  - otherwise
+#fd SIDE EFFECTS
+      None
+#endfn
+*/
+
+int vp_cascade_motion(const VP_MOTION* InA, const VP_MOTION* InB,VP_MOTION* Out)
+{
+    /* ==> This is a poor implementation of matrix multiplication.
+       Writing the formula out in closed form is unnecessarily complicated
+       and mistakes are easy to make. */
+  VP_PAR mxx,mxy,mxz,mxw;
+  VP_PAR myx,myy,myz,myw;
+  VP_PAR mzx,mzy,mzz,mzw;
+  VP_PAR mwx,mwy,mwz,mww;
+
+  /* check for non-empty structures structure */
+  if (((VP_MOTION *) NULL == InA) || ((VP_MOTION *) NULL == InB) ||
+      ((VP_MOTION *) NULL == Out)) {
+    return FALSE;
+  }
+
+  if (InA->type>VP_MOTION_PROJ_3D) {
+    return FALSE;
+  }
+
+  if (InB->type>VP_MOTION_PROJ_3D) {
+    return FALSE;
+  }
+
+  mxx = MXX(*InB)*MXX(*InA)+MXY(*InB)*MYX(*InA)+MXZ(*InB)*MZX(*InA)+MXW(*InB)*MWX(*InA);
+  mxy = MXX(*InB)*MXY(*InA)+MXY(*InB)*MYY(*InA)+MXZ(*InB)*MZY(*InA)+MXW(*InB)*MWY(*InA);
+  mxz = MXX(*InB)*MXZ(*InA)+MXY(*InB)*MYZ(*InA)+MXZ(*InB)*MZZ(*InA)+MXW(*InB)*MWZ(*InA);
+  mxw = MXX(*InB)*MXW(*InA)+MXY(*InB)*MYW(*InA)+MXZ(*InB)*MZW(*InA)+MXW(*InB)*MWW(*InA);
+  myx = MYX(*InB)*MXX(*InA)+MYY(*InB)*MYX(*InA)+MYZ(*InB)*MZX(*InA)+MYW(*InB)*MWX(*InA);
+  myy = MYX(*InB)*MXY(*InA)+MYY(*InB)*MYY(*InA)+MYZ(*InB)*MZY(*InA)+MYW(*InB)*MWY(*InA);
+  myz = MYX(*InB)*MXZ(*InA)+MYY(*InB)*MYZ(*InA)+MYZ(*InB)*MZZ(*InA)+MYW(*InB)*MWZ(*InA);
+  myw = MYX(*InB)*MXW(*InA)+MYY(*InB)*MYW(*InA)+MYZ(*InB)*MZW(*InA)+MYW(*InB)*MWW(*InA);
+  mzx = MZX(*InB)*MXX(*InA)+MZY(*InB)*MYX(*InA)+MZZ(*InB)*MZX(*InA)+MZW(*InB)*MWX(*InA);
+  mzy = MZX(*InB)*MXY(*InA)+MZY(*InB)*MYY(*InA)+MZZ(*InB)*MZY(*InA)+MZW(*InB)*MWY(*InA);
+  mzz = MZX(*InB)*MXZ(*InA)+MZY(*InB)*MYZ(*InA)+MZZ(*InB)*MZZ(*InA)+MZW(*InB)*MWZ(*InA);
+  mzw = MZX(*InB)*MXW(*InA)+MZY(*InB)*MYW(*InA)+MZZ(*InB)*MZW(*InA)+MZW(*InB)*MWW(*InA);
+  mwx = MWX(*InB)*MXX(*InA)+MWY(*InB)*MYX(*InA)+MWZ(*InB)*MZX(*InA)+MWW(*InB)*MWX(*InA);
+  mwy = MWX(*InB)*MXY(*InA)+MWY(*InB)*MYY(*InA)+MWZ(*InB)*MZY(*InA)+MWW(*InB)*MWY(*InA);
+  mwz = MWX(*InB)*MXZ(*InA)+MWY(*InB)*MYZ(*InA)+MWZ(*InB)*MZZ(*InA)+MWW(*InB)*MWZ(*InA);
+  mww = MWX(*InB)*MXW(*InA)+MWY(*InB)*MYW(*InA)+MWZ(*InB)*MZW(*InA)+MWW(*InB)*MWW(*InA);
+
+  MXX(*Out)=mxx; MXY(*Out)=mxy; MXZ(*Out)=mxz; MXW(*Out)=mxw;
+  MYX(*Out)=myx; MYY(*Out)=myy; MYZ(*Out)=myz; MYW(*Out)=myw;
+  MZX(*Out)=mzx; MZY(*Out)=mzy; MZZ(*Out)=mzz; MZW(*Out)=mzw;
+  MWX(*Out)=mwx; MWY(*Out)=mwy; MWZ(*Out)=mwz; MWW(*Out)=mww;
+  /* VP_NORMALIZE(*Out); */
+  Out->type= (InA->type > InB->type) ? InA->type : InB->type;
+  Out->refid=InA->refid;
+  Out->insid=InB->insid;
+
+  return TRUE;
+}
+
+/*
+* ===================================================================
+#fn vp_copy_motion
+#ft Copies the source motion to the destination motion.
+#fd DEFINITION
+    void
+    vp_copy_motion  (const VP_MOTION *src, VP_MOTION *dst)
+#fd PURPOSE
+    Copies the source motion to the destination motion.
+        It is OK if src == dst.
+    NOTE THAT THE SOURCE IS THE FIRST ARGUMENT.
+    This is different from some of the other VP
+    copy functions.
+#fd INPUTS
+    src is the source motion
+    dst is the destination motion
+#fd RETURNS
+    void
+#endfn
+*/
+void vp_copy_motion  (const VP_MOTION *src, VP_MOTION *dst)
+{
+  /* Use memmove rather than memcpy because it handles overlapping memory
+     OK. */
+  memmove(dst, src, sizeof(VP_MOTION));
+  return;
+} /* vp_copy_motion() */
+
+#define VP_SQR(x)   ( (x)*(x) )
+double vp_motion_cornerdiff(const VP_MOTION *mot_a, const VP_MOTION *mot_b,
+                     int xo, int yo, int w, int h)
+{
+  double ax1, ay1, ax2, ay2, ax3, ay3, ax4, ay4;
+  double bx1, by1, bx2, by2, bx3, by3, bx4, by4;
+  double err;
+
+  /*lint -e639 -e632 -e633 */
+  VP_WARP_POINT_2D(xo, yo,         *mot_a, ax1, ay1);
+  VP_WARP_POINT_2D(xo+w-1, yo,     *mot_a, ax2, ay2);
+  VP_WARP_POINT_2D(xo+w-1, yo+h-1, *mot_a, ax3, ay3);
+  VP_WARP_POINT_2D(xo, yo+h-1,     *mot_a, ax4, ay4);
+  VP_WARP_POINT_2D(xo, yo,         *mot_b, bx1, by1);
+  VP_WARP_POINT_2D(xo+w-1, yo,     *mot_b, bx2, by2);
+  VP_WARP_POINT_2D(xo+w-1, yo+h-1, *mot_b, bx3, by3);
+  VP_WARP_POINT_2D(xo, yo+h-1,     *mot_b, bx4, by4);
+  /*lint +e639 +e632 +e633 */
+
+  err = 0;
+  err += (VP_SQR(ax1 - bx1) + VP_SQR(ay1 - by1));
+  err += (VP_SQR(ax2 - bx2) + VP_SQR(ay2 - by2));
+  err += (VP_SQR(ax3 - bx3) + VP_SQR(ay3 - by3));
+  err += (VP_SQR(ax4 - bx4) + VP_SQR(ay4 - by4));
+
+  return(sqrt(err));
+}
+
+int vp_zoom_motion2d(VP_MOTION* in, VP_MOTION* out,
+                 int n, int w, int h, double zoom)
+{
+  int ii;
+  VP_PAR inv_zoom;
+  VP_PAR cx, cy;
+  VP_MOTION R2r,R2f;
+  VP_MOTION *res;
+
+  /* check for non-empty structures structure */
+  if (((VP_MOTION *) NULL == in)||(zoom <= 0.0)||(w <= 0)||(h <= 0)) {
+    return FALSE;
+  }
+
+  /* ==> Not sure why the special case of out=NULL is necessary.  Why couldn't
+     the caller just pass the same pointer for both in and out? */
+  res = ((VP_MOTION *) NULL == out)?in:out;
+
+  cx = (VP_PAR) (w/2.0);
+  cy = (VP_PAR) (h/2.0);
+
+  VP_MOTION_ID(R2r);
+  inv_zoom = (VP_PAR)(1.0/zoom);
+  MXX(R2r) = inv_zoom;
+  MYY(R2r) = inv_zoom;
+  MXW(R2r)=cx*(((VP_PAR)1.0) - inv_zoom);
+  MYW(R2r)=cy*(((VP_PAR)1.0) - inv_zoom);
+
+  VP_KEEP_AFFINE_2D(R2r);
+
+  for(ii=0;ii<n;ii++) {
+    (void) vp_cascade_motion(&R2r,in+ii,&R2f);
+    res[ii]=R2f;
+  }
+
+  return TRUE;
+} /* vp_zoom_motion2d() */
+
+/* =================================================================== */
+/* end vp_motionmodel.c */
diff --git a/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.h b/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.h
new file mode 100644
index 0000000..a63ac00
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbreg/vp_motionmodel.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+/*
+#sourcefile  vp_motionmodel.h
+#category    warp
+#description general motion model for tranlation/affine/projective
+#title       motion-model
+#parentlink  hindex.html
+*
+* Copyright 1998 Sarnoff Corporation
+* All Rights Reserved
+*
+* Modification History
+*      Date: 02/13/98
+*      Author: supuns
+*      Shop Order: 15491 001
+*              @(#) $Id: vp_motionmodel.h,v 1.4 2011/06/17 14:04:33 mbansal Exp $
+*/
+
+#ifndef VP_MOTIONMODEL_H
+#define VP_MOTIONMODEL_H
+#include <stdio.h>
+
+#define         FALSE           0
+#define         TRUE            1
+
+#if 0 /* Moved mottomat.c and mattomot_d.c from vpmotion.h to vpcompat.h
+     in order to remove otherwise unnecessary dependency of vpmotion,
+     vpwarp, and newvpio on vpmath */
+#ifndef VPMATH_H
+#include "vpmath.h"
+#endif
+#endif
+
+#if 0
+#ifndef VP_WARP_H
+#include "vp_warp.h"
+#endif
+#endif
+/*
+
+#htmlstart
+# ===================================================================
+#h 1 Introduction
+
+  This defines a motion model that can describe translation,
+  affine, and projective projective 3d and 3d view transforms.
+
+  The main structure VP_MOTION contains a 16 parameter array (That
+  can be considered as elements of a 4x4 matrix) and a type field
+  which can be one of VP_MOTION_NONE,VP_MOTION_TRANSLATION,
+  VP_MOTION_AFFINE, VP_MOTION_PROJECTIVE,VP_MOTION_PROJ_3D or
+  VP_MOTION_VIEW_3D. (These are defined using enums with gaps of 10
+  so that subsets of these motions that are still consistant can be
+  added in between. Motion models that are inconsistant with this set
+  should be added at the end so the routines can hadle them
+  independently.
+
+  The transformation VP_MOTION_NONE,VP_MOTION_TRANSLATION,
+  VP_MOTION_AFFINE, VP_MOTION_PROJECTIVE, VP_MOTION_PROJ_3D and
+  VP_MOTION_SEMI_PROJ_3D would map a point P={x,y,z,w} to a new point
+  P'={x',y',z',w'} using a motion model M such that P'= M.par * P.
+  Where M.par is thought of as  elements of a 4x4 matrix ordered row
+  by row. The interpretation of all models except VP_MOTION_SEMI_PROJ_3D
+  is taken to be mapping of a 3d point P"={x",y",z"} which is obtained
+  from the normalization {x'/w',y'/w',z'/w'}. In the VP_MOTION_SEMI_PROJ_3D
+  the mapping to a point P"={x",y",z"} is obtained from the normalization
+  {x'/w',y'/w',z'}. All these motion models have the property that they
+  can be inverted using 4x4 matrices. Except for the VP_MOTION_SEMI_PROJ_3D all
+  other types can also be cascaded using 4x4 matrices.
+
+  Specific macros and functions have been provided to handle 2d instances
+  of these functions. As the parameter interpretations can change when adding
+  new motion models it is HIGHLY RECOMMENDED that you use the macros MXX,MXY..
+  ect. to interpret each motion component.
+#pre
+*/
+
+/*
+#endpre
+# ===================================================================
+#h 1 Typedef and Struct Declarations
+#pre
+*/
+
+#define VP_MAX_MOTION_PAR 16
+
+typedef double VP_PAR;
+typedef VP_PAR VP_TRS[VP_MAX_MOTION_PAR];
+
+/* Do not add any motion models before VP_MOTION_PROJECTIVE */
+/* The order is assumed in vp functions */
+enum VP_MOTION_MODEL {
+  VP_MOTION_NONE=0,
+  VP_MOTION_TRANSLATION=10,
+  VP_MOTION_SCALE=11,
+  VP_MOTION_ROTATE=12,
+  VP_MOTION_X_SHEAR=13,
+  VP_MOTION_Y_SHEAR=14,
+  VP_MOTION_SIMILARITY=15,
+  VP_MOTION_AFFINE=20,
+  VP_MOTION_PROJECTIVE=30,
+  VP_MOTION_PROJ_3D=40,
+  VP_MOTION_SEMI_PROJ_3D=80,
+  VP_SIMILARITY=100,
+  VP_VFE_AFFINE=120
+};
+
+#define VP_REFID -1   /* Default ID used for reference frame */
+
+typedef struct {
+  VP_TRS par;            /* Contains the motion paramerers.
+                For the standard motion types this is
+                represented as 16 number that refer
+                to a 4x4 matrix */
+  enum VP_MOTION_MODEL type;
+  int refid;            /* Reference frame ( takes a point in refid frame
+               and moves it by the par to get a point in insid
+               frame ) */
+  int insid;            /* Inspection frame */
+} VP_MOTION;
+
+//typedef VP_LIST VP_MOTION_LIST;
+/*
+#endpre
+# ===================================================================
+#h 1 Constant Declarations
+*/
+
+/* Macros related to the 4x4 matrix parameters */
+#define MXX(m) (m).par[0]
+#define MXY(m) (m).par[1]
+#define MXZ(m) (m).par[2]
+#define MXW(m) (m).par[3]
+#define MYX(m) (m).par[4]
+#define MYY(m) (m).par[5]
+#define MYZ(m) (m).par[6]
+#define MYW(m) (m).par[7]
+#define MZX(m) (m).par[8]
+#define MZY(m) (m).par[9]
+#define MZZ(m) (m).par[10]
+#define MZW(m) (m).par[11]
+#define MWX(m) (m).par[12]
+#define MWY(m) (m).par[13]
+#define MWZ(m) (m).par[14]
+#define MWW(m) (m).par[15]
+
+/* The do {...} while (0) technique creates a statement that can be used legally
+   in an if-else statement.  See "Swallowing the semicolon",
+   http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC23 */
+/* Initialize the Motion to be Identity */
+#define VP_MOTION_ID(m) do {\
+  MXX(m)=MYY(m)=MZZ(m)=MWW(m)=(VP_PAR)1.0; \
+  MXY(m)=MXZ(m)=MXW(m)=(VP_PAR)0.0; \
+  MYX(m)=MYZ(m)=MYW(m)=(VP_PAR)0.0; \
+  MZX(m)=MZY(m)=MZW(m)=(VP_PAR)0.0; \
+  MWX(m)=MWY(m)=MWZ(m)=(VP_PAR)0.0; \
+(m).type = VP_MOTION_TRANSLATION; } while (0)
+
+/* Initialize without altering the translation components */
+#define VP_KEEP_TRANSLATION_3D(m) do {\
+  MXX(m)=MYY(m)=MZZ(m)=MWW(m)=(VP_PAR)1.0; \
+  MXY(m)=MXZ(m)=(VP_PAR)0.0; \
+  MYX(m)=MYZ(m)=(VP_PAR)0.0; \
+  MZX(m)=MZY(m)=(VP_PAR)0.0; \
+  MWX(m)=MWY(m)=MWZ(m)=(VP_PAR)0.0; \
+  (m).type = VP_MOTION_PROJ_3D; } while (0)
+
+/* Initialize without altering the 2d translation components */
+#define VP_KEEP_TRANSLATION_2D(m) do {\
+  VP_KEEP_TRANSLATION_3D(m); MZW(m)=(VP_PAR)0.0; (m).type= VP_MOTION_TRANSLATION;} while (0)
+
+/* Initialize without altering the affine & translation components */
+#define VP_KEEP_AFFINE_3D(m) do {\
+  MWX(m)=MWY(m)=MWZ(m)=(VP_PAR)0.0; MWW(m)=(VP_PAR)1.0; \
+  (m).type = VP_MOTION_PROJ_3D; } while (0)
+
+/* Initialize without altering the 2d affine & translation components */
+#define VP_KEEP_AFFINE_2D(m) do {\
+  VP_KEEP_AFFINE_3D(m); \
+  MXZ(m)=MYZ(m)=(VP_PAR)0.0; MZZ(m)=(VP_PAR)1.0; \
+  MZX(m)=MZY(m)=MZW(m)=(VP_PAR)0.0; \
+  (m).type = VP_MOTION_AFFINE; } while (0)
+
+/* Initialize without altering the 2d projective parameters */
+#define VP_KEEP_PROJECTIVE_2D(m) do {\
+  MXZ(m)=MYZ(m)=(VP_PAR)0.0; MZZ(m)=(VP_PAR)1.0; \
+  MZX(m)=MZY(m)=MZW(m)=MWZ(m)=(VP_PAR)0.0; \
+  (m).type = VP_MOTION_PROJECTIVE; } while (0)
+
+/* Warp a 2d point (assuming the z component is zero) */
+#define VP_WARP_POINT_2D(inx,iny,m,outx,outy) do {\
+  VP_PAR vpTmpWarpPnt___= MWX(m)*(inx)+MWY(m)*(iny)+MWW(m); \
+  outx = (MXX(m)*((VP_PAR)inx)+MXY(m)*((VP_PAR)iny)+MXW(m))/vpTmpWarpPnt___; \
+  outy = (MYX(m)*((VP_PAR)inx)+MYY(m)*((VP_PAR)iny)+MYW(m))/vpTmpWarpPnt___; } while (0)
+
+/* Warp a 3d point */
+#define VP_WARP_POINT_3D(inx,iny,inz,m,outx,outy,outz) do {\
+  VP_PAR vpTmpWarpPnt___= MWX(m)*(inx)+MWY(m)*(iny)+MWZ(m)*((VP_PAR)inz)+MWW(m); \
+  outx = (MXX(m)*((VP_PAR)inx)+MXY(m)*((VP_PAR)iny)+MXZ(m)*((VP_PAR)inz)+MXW(m))/vpTmpWarpPnt___; \
+  outy = (MYX(m)*((VP_PAR)inx)+MYY(m)*((VP_PAR)iny)+MYZ(m)*((VP_PAR)inz)+MYW(m))/vpTmpWarpPnt___; \
+  outz = MZX(m)*((VP_PAR)inx)+MZY(m)*((VP_PAR)iny)+MZZ(m)*((VP_PAR)inz)+MZW(m); \
+  if ((m).type==VP_MOTION_PROJ_3D) outz/=vpTmpWarpPnt___; } while (0)
+
+/* Projections of each component */
+#define VP_PROJW_3D(m,x,y,z,f)   ( MWX(m)*(x)+MWY(m)*(y)+MWZ(m)*(z)+MWW(m) )
+#define VP_PROJX_3D(m,x,y,z,f,w) ((MXX(m)*(x)+MXY(m)*(y)+MXZ(m)*(z)+MXW(m))/(w))
+#define VP_PROJY_3D(m,x,y,z,f,w) ((MYX(m)*(x)+MYY(m)*(y)+MYZ(m)*(z)+MYW(m))/(w))
+#define VP_PROJZ_3D(m,x,y,z,f,w) ((MZX(m)*(x)+MZY(m)*(y)+MZZ(m)*(z)+MZW(m))/(w))
+
+/* Scale Down a matrix by Sfactor */
+#define VP_SCALEDOWN(m,Sfactor) do { \
+  MXW(m) /= (VP_PAR)Sfactor; MWX(m) *= (VP_PAR)Sfactor; \
+  MYW(m) /= (VP_PAR)Sfactor; MWY(m) *= (VP_PAR)Sfactor; \
+  MZW(m) /= (VP_PAR)Sfactor; MWZ(m) *= (VP_PAR)Sfactor; } while (0)
+
+/* Scale Up a matrix by Sfactor */
+#define VP_SCALEUP(m,Sfactor) do { \
+  MXW(m) *= (VP_PAR)Sfactor; MWX(m) /= (VP_PAR)Sfactor; \
+  MYW(m) *= (VP_PAR)Sfactor; MWY(m) /= (VP_PAR)Sfactor; \
+  MZW(m) *= (VP_PAR)Sfactor; MWZ(m) /= (VP_PAR)Sfactor; } while (0)
+
+/* Normalize the transformation matrix so that MWW is 1 */
+#define VP_NORMALIZE(m) if (MWW(m)!=(VP_PAR)0.0) do { \
+  MXX(m)/=MWW(m); MXY(m)/=MWW(m); MXZ(m)/=MWW(m); MXW(m)/= MWW(m); \
+  MYX(m)/=MWW(m); MYY(m)/=MWW(m); MYZ(m)/=MWW(m); MYW(m)/= MWW(m); \
+  MZX(m)/=MWW(m); MZY(m)/=MWW(m); MZZ(m)/=MWW(m); MZW(m)/= MWW(m); \
+  MWX(m)/=MWW(m); MWY(m)/=MWW(m); MWZ(m)/=MWW(m); MWW(m) = (VP_PAR)1.0; } while (0)
+
+#define VP_PRINT_TRANS(msg,b) do { \
+  fprintf(stderr, \
+      "%s\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n", \
+      msg, \
+      MXX(b),MXY(b),MXZ(b),MXW(b),  \
+      MYX(b),MYY(b),MYZ(b),MYW(b),  \
+      MZX(b),MZY(b),MZZ(b),MZW(b),  \
+      MWX(b),MWY(b),MWZ(b),MWW(b)); \
+} while (0)
+
+/* w' projection given a point x,y,0,f */
+#define VP_PROJZ(m,x,y,f) ( \
+    MWX(m)*((VP_PAR)x)+MWY(m)*((VP_PAR)y)+MWW(m)*((VP_PAR)f))
+
+/* X Projection given a point x,y,0,f and w' */
+#define VP_PROJX(m,x,y,w,f) (\
+   (MXX(m)*((VP_PAR)x)+MXY(m)*((VP_PAR)y)+MXW(m)*((VP_PAR)f))/((VP_PAR)w))
+
+/* Y Projection given a point x,y,0,f and the w' */
+#define VP_PROJY(m,x,y,w,f) (\
+  (MYX(m)*((VP_PAR)x)+MYY(m)*((VP_PAR)y)+MYW(m)*((VP_PAR)f))/((VP_PAR)w))
+
+/* Set the reference id for a motion */
+#define VP_SET_REFID(m,id) do { (m).refid=id; } while (0)
+
+/* Set the inspection id for a motion */
+#define VP_SET_INSID(m,id) do { (m).insid=id; } while (0)
+
+void vp_copy_motion  (const VP_MOTION *src, VP_MOTION *dst);
+int vp_invert_motion(const VP_MOTION* in,VP_MOTION* out);
+int vp_cascade_motion(const VP_MOTION* InAB, const VP_MOTION* InBC,VP_MOTION* OutAC);
+int vp_zoom_motion2d(VP_MOTION* in, VP_MOTION* out,
+              int n, int w, int h, double zoom);
+double vp_motion_cornerdiff(const VP_MOTION *mot_a, const VP_MOTION *mot_b,
+                     int xo, int yo, int w, int h);
+
+#endif /* VP_MOTIONMODEL_H */
+/* =================================================================== */
+/* end vp_motionmodel.h */
diff --git a/jni_mosaic/feature_stab/src/dbregtest/PgmImage.cpp b/jni_mosaic/feature_stab/src/dbregtest/PgmImage.cpp
new file mode 100644
index 0000000..0891cfd
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/PgmImage.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "PgmImage.h"
+#include <cassert>
+
+using namespace std;
+
+PgmImage::PgmImage(std::string filename) :
+m_w(0),m_h(0),m_colors(255),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
+{
+    if ( !ReadPGM(filename) )
+        return;
+}
+
+PgmImage::PgmImage(int w, int h, int format) :
+m_colors(255),m_w(w),m_h(h),m_format(format),m_over_allocation(256)
+{
+    SetFormat(format);
+}
+
+PgmImage::PgmImage(unsigned char *data, int w, int h) :
+m_colors(255),m_w(w),m_h(h),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
+{
+    SetData(data);
+}
+
+PgmImage::PgmImage(std::vector<unsigned char> &data, int w, int h) :
+m_colors(255),m_w(w),m_h(h),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
+{
+    if ( data.size() == w*h )
+        SetData(&data[0]);
+    else
+        //throw (std::exception("Size of data is not w*h."));
+        throw (std::exception());
+}
+
+PgmImage::PgmImage(const PgmImage &im) :
+m_colors(255),m_w(0),m_h(0),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
+{
+    DeepCopy(im, *this);
+}
+
+PgmImage& PgmImage::operator= (const PgmImage &im)
+{
+    if (this == &im) return *this;
+    DeepCopy(im, *this);
+    return *this;
+}
+
+void PgmImage::DeepCopy(const PgmImage& src, PgmImage& dst)
+{
+    dst.m_data = src.m_data;
+
+    // PGM data
+    dst.m_w = src.m_w;
+    dst.m_h = src.m_h;
+    dst.m_format = src.m_format;
+    dst.m_colors = src.m_colors;
+
+    dst.m_comment = src.m_comment;
+    SetupRowPointers();
+}
+
+PgmImage::~PgmImage()
+{
+
+}
+
+void PgmImage::SetFormat(int format)
+{
+    m_format = format;
+
+    switch (format)
+    {
+    case PGM_BINARY_GRAYMAP:
+        m_data.resize(m_w*m_h+m_over_allocation);
+        break;
+    case PGM_BINARY_PIXMAP:
+        m_data.resize(m_w*m_h*3+m_over_allocation);
+        break;
+    default:
+        return;
+        break;
+    }
+    SetupRowPointers();
+}
+
+void PgmImage::SetData(const unsigned char * data)
+{
+    m_data.resize(m_w*m_h+m_over_allocation);
+    memcpy(&m_data[0],data,m_w*m_h);
+    SetupRowPointers();
+}
+
+bool PgmImage::ReadPGM(const std::string filename)
+{
+    ifstream in(filename.c_str(),std::ios::in | std::ios::binary);
+    if ( !in.is_open() )
+        return false;
+
+    // read the header:
+    string format_header,size_header,colors_header;
+
+    getline(in,format_header);
+    stringstream s;
+    s << format_header;
+
+    s >> format_header >> m_w >> m_h >> m_colors;
+    s.clear();
+
+    if ( m_w == 0 )
+    {
+        while ( in.peek() == '#' )
+            getline(in,m_comment);
+
+        getline(in,size_header);
+
+        while ( in.peek() == '#' )
+            getline(in,m_comment);
+
+            m_colors = 0;
+
+        // parse header
+        s << size_header;
+        s >> m_w >> m_h >> m_colors;
+        s.clear();
+
+        if ( m_colors == 0 )
+        {
+            getline(in,colors_header);
+            s << colors_header;
+            s >> m_colors;
+        }
+    }
+
+    if ( format_header == "P5" )
+        m_format = PGM_BINARY_GRAYMAP;
+    else if (format_header == "P6" )
+        m_format = PGM_BINARY_PIXMAP;
+    else
+        m_format = PGM_FORMAT_INVALID;
+
+    switch(m_format)
+    {
+    case(PGM_BINARY_GRAYMAP):
+        m_data.resize(m_w*m_h+m_over_allocation);
+        in.read((char *)(&m_data[0]),m_data.size());
+        break;
+    case(PGM_BINARY_PIXMAP):
+        m_data.resize(m_w*m_h*3+m_over_allocation);
+        in.read((char *)(&m_data[0]),m_data.size());
+        break;
+    default:
+        return false;
+        break;
+    }
+    in.close();
+
+    SetupRowPointers();
+
+    return true;
+}
+
+bool PgmImage::WritePGM(const std::string filename, const std::string comment)
+{
+    string format_header;
+
+    switch(m_format)
+    {
+    case PGM_BINARY_GRAYMAP:
+        format_header = "P5\n";
+        break;
+    case PGM_BINARY_PIXMAP:
+        format_header = "P6\n";
+        break;
+    default:
+        return false;
+        break;
+    }
+
+    ofstream out(filename.c_str(),std::ios::out |ios::binary);
+    out << format_header << "# " << comment << '\n' << m_w << " " << m_h << '\n' << m_colors << '\n';
+
+    out.write((char *)(&m_data[0]), m_data.size());
+
+    out.close();
+
+    return true;
+}
+
+void PgmImage::SetupRowPointers()
+{
+    int i;
+    m_rows.resize(m_h);
+
+    switch (m_format)
+    {
+    case PGM_BINARY_GRAYMAP:
+        for(i=0;i<m_h;i++)
+        {
+            m_rows[i]=&m_data[m_w*i];
+        }
+        break;
+    case PGM_BINARY_PIXMAP:
+        for(i=0;i<m_h;i++)
+        {
+            m_rows[i]=&m_data[(m_w*3)*i];
+        }
+        break;
+    }
+}
+
+void PgmImage::ConvertToGray()
+{
+    if ( m_format != PGM_BINARY_PIXMAP ) return;
+
+    // Y = 0.3*R + 0.59*G + 0.11*B;
+    for ( int i = 0; i < m_w*m_h; ++i )
+        m_data[i] = (unsigned char)(0.3*m_data[3*i]+0.59*m_data[3*i+1]+0.11*m_data[3*i+2]);
+
+    m_data.resize(m_w*m_h+m_over_allocation);
+    m_format = PGM_BINARY_GRAYMAP;
+
+    SetupRowPointers();
+}
+
+std::ostream& operator<< (std::ostream& o, const PgmImage& im)
+{
+    o << "PGM Image Info:\n";
+    o << "Size: " << im.m_w << " x " << im.m_h << "\n";
+    o << "Comment: " << im.m_comment << "\n";
+    switch (im.m_format)
+    {
+    case PgmImage::PGM_BINARY_PIXMAP:
+        o << "Format: RGB binary pixmap";
+        break;
+    case PgmImage::PGM_BINARY_GRAYMAP:
+        o << "Format: PPM binary graymap";
+        break;
+    default:
+        o << "Format: Invalid";
+        break;
+    }
+    o << endl;
+    return o;
+}
diff --git a/jni_mosaic/feature_stab/src/dbregtest/PgmImage.h b/jni_mosaic/feature_stab/src/dbregtest/PgmImage.h
new file mode 100644
index 0000000..d4d1eeb
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/PgmImage.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#pragma once
+
+#include <vector>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <memory.h>
+
+/*!
+ * Simple class to manipulate PGM/PPM images. Not suitable for heavy lifting.
+ */
+class PgmImage
+{
+    friend std::ostream& operator<< (std::ostream& o, const PgmImage& im);
+public:
+    enum {PGM_BINARY_GRAYMAP,PGM_BINARY_PIXMAP,PGM_FORMAT_INVALID};
+    /*!
+    * Constructor from a PGM file name.
+    */
+    PgmImage(std::string filename);
+    /*!
+    * Constructor to allocate an image of given size and type.
+    */
+    PgmImage(int w, int h, int format = PGM_BINARY_GRAYMAP);
+    /*!
+    * Constructor to allocate an image of given size and copy the data in.
+    */
+    PgmImage(unsigned char *data, int w, int h);
+    /*!
+    * Constructor to allocate an image of given size and copy the data in.
+    */
+    PgmImage(std::vector<unsigned char> &data, int w, int h);
+
+    PgmImage(const PgmImage &im);
+
+    PgmImage& operator= (const PgmImage &im);
+    ~PgmImage();
+
+    int GetHeight() const { return m_h; }
+    int GetWidth() const { return m_w; }
+
+    //! Copy pixels from data pointer
+    void SetData(const unsigned char * data);
+
+    //! Get a data pointer to unaligned memory area
+    unsigned char * GetDataPointer() { if ( m_data.size() > 0 ) return &m_data[0]; else return NULL; }
+    unsigned char ** GetRowPointers() { if ( m_rows.size() == m_h ) return &m_rows[0]; else return NULL; }
+
+    //! Read a PGM file from disk
+    bool ReadPGM(const std::string filename);
+    //! Write a PGM file to disk
+    bool WritePGM(const std::string filename, const std::string comment="");
+
+    //! Get image format (returns PGM_BINARY_GRAYMAP, PGM_BINARY_PIXMAP or PGM_FORMAT_INVALID)
+    int GetFormat() const { return m_format; }
+
+    //! Set image format (returns PGM_BINARY_GRAYMAP, PGM_BINARY_PIXMAP). Image data becomes invalid.
+    void SetFormat(int format);
+
+    //! If the image is PGM_BINARY_PIXMAP, convert it to PGM_BINARY_GRAYMAP via Y = 0.3*R + 0.59*G + 0.11*B.
+    void ConvertToGray();
+protected:
+    // Generic functions:
+    void DeepCopy(const PgmImage& src, PgmImage& dst);
+    void SetupRowPointers();
+
+    // PGM data
+    int m_w;
+    int m_h;
+    int m_format;
+    int m_colors;
+    int m_over_allocation;
+    std::vector<unsigned char> m_data;
+    std::string m_comment;
+
+    std::vector<unsigned char *> m_rows;
+};
+
+std::ostream& operator<< (std::ostream& o, const PgmImage& im);
diff --git a/jni_mosaic/feature_stab/src/dbregtest/dbregtest.cpp b/jni_mosaic/feature_stab/src/dbregtest/dbregtest.cpp
new file mode 100644
index 0000000..5087362
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/dbregtest.cpp
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// $Id: dbregtest.cpp,v 1.24 2011/06/17 14:04:33 mbansal Exp $
+#include "stdafx.h"
+#include "PgmImage.h"
+#include "../dbreg/dbreg.h"
+#include "../dbreg/dbstabsmooth.h"
+#include <db_utilities_camera.h>
+
+#include <iostream>
+#include <iomanip>
+
+#if PROFILE
+    #include <sys/time.h>
+#endif
+
+
+using namespace std;
+
+const int DEFAULT_NR_CORNERS=500;
+const double DEFAULT_MAX_DISPARITY=0.2;
+const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_AFFINE;
+//const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_R_T;
+//const int DEFAULT_MOTION_MODEL=DB_HOMOGRAPHY_TYPE_TRANSLATION;
+const bool DEFAULT_QUARTER_RESOLUTION=false;
+const unsigned int DEFAULT_REFERENCE_UPDATE_PERIOD=3;
+const bool DEFAULT_DO_MOTION_SMOOTHING = false;
+const double DEFAULT_MOTION_SMOOTHING_GAIN = 0.75;
+const bool DEFAULT_LINEAR_POLISH = false;
+const int DEFAULT_MAX_ITERATIONS = 10;
+
+void usage(string name) {
+
+  const char *helpmsg[] = {
+    "Function: point-based frame to reference registration.",
+    "  -m [rt,a,p]  : motion model, rt = rotation+translation, a = affine (default = affine).",
+    "  -c <int>   : number of corners (default 1000).",
+    "  -d <double>: search disparity as portion of image size (default 0.1).",
+    "  -q         : quarter the image resolution (i.e. half of each dimension) (default on)",
+    "  -r <int>   : the period (in nr of frames) for reference frame updates (default = 5)",
+    "  -s <0/1>   : motion smoothing (1 activates motion smoothing, 0 turns it off - default value = 1)",
+    "  -g <double>: motion smoothing gain, only used if smoothing is on (default value =0.75)",
+    NULL
+  };
+
+  cerr << "Usage: " << name << " [options] image_list.txt" << endl;
+
+  const char **p = helpmsg;
+
+  while (*p)
+  {
+    cerr << *p++ << endl;
+  }
+}
+
+void parse_cmd_line(stringstream& cmdline,
+            const int argc,
+            const string& progname,
+            string& image_list_file_name,
+            int& nr_corners,
+            double& max_disparity,
+            int& motion_model_type,
+            bool& quarter_resolution,
+            unsigned int& reference_update_period,
+            bool& do_motion_smoothing,
+            double& motion_smoothing_gain
+            );
+
+int main(int argc, char* argv[])
+{
+  int    nr_corners = DEFAULT_NR_CORNERS;
+  double max_disparity = DEFAULT_MAX_DISPARITY;
+  int    motion_model_type = DEFAULT_MOTION_MODEL;
+  bool   quarter_resolution = DEFAULT_QUARTER_RESOLUTION;
+
+  unsigned int reference_update_period = DEFAULT_REFERENCE_UPDATE_PERIOD;
+
+  bool   do_motion_smoothing = DEFAULT_DO_MOTION_SMOOTHING;
+  double motion_smoothing_gain = DEFAULT_MOTION_SMOOTHING_GAIN;
+  const bool DEFAULT_USE_SMALLER_MATCHING_WINDOW = true;
+
+  int default_nr_samples = DB_DEFAULT_NR_SAMPLES/5;
+
+  bool   use_smaller_matching_window = DEFAULT_USE_SMALLER_MATCHING_WINDOW;
+
+
+  bool   linear_polish = DEFAULT_LINEAR_POLISH;
+
+  if (argc < 2) {
+    usage(argv[0]);
+    exit(1);
+  }
+
+  stringstream cmdline;
+  string progname(argv[0]);
+  string image_list_file_name;
+
+#if PROFILE
+  timeval ts1, ts2, ts3, ts4;
+#endif
+
+  // put the options and image list file name into the cmdline stringstream
+  for (int c = 1; c < argc; c++)
+  {
+    cmdline << argv[c] << " ";
+  }
+
+  parse_cmd_line(cmdline, argc, progname, image_list_file_name, nr_corners, max_disparity, motion_model_type,quarter_resolution,reference_update_period,do_motion_smoothing,motion_smoothing_gain);
+
+  ifstream in(image_list_file_name.c_str(),ios::in);
+
+  if ( !in.is_open() )
+  {
+    cerr << "Could not open file " << image_list_file_name << ".  Exiting" << endl;
+
+    return false;
+  }
+
+  // feature-based image registration class:
+  db_FrameToReferenceRegistration reg;
+//  db_StabilizationSmoother stab_smoother;
+
+  // input file name:
+  string file_name;
+
+  // look-up tables for image warping:
+  float ** lut_x = NULL, **lut_y = NULL;
+
+  // if the images are color, the input is saved in color_ref:
+  PgmImage color_ref(0,0);
+
+  // image width, height:
+  int w,h;
+
+  int frame_number = 0;
+
+  while ( !in.eof() )
+  {
+    getline(in,file_name);
+
+    PgmImage ref(file_name);
+
+    if ( ref.GetDataPointer() == NULL )
+    {
+      cerr << "Could not open image" << file_name << ". Exiting." << endl;
+      return -1;
+    }
+
+    cout << ref << endl;
+
+    // color format:
+    int format = ref.GetFormat();
+
+    // is the input image color?:
+    bool color = format == PgmImage::PGM_BINARY_PIXMAP;
+
+    w = ref.GetWidth();
+    h = ref.GetHeight();
+
+    if ( !reg.Initialized() )
+    {
+      reg.Init(w,h,motion_model_type,DEFAULT_MAX_ITERATIONS,linear_polish,quarter_resolution,DB_POINT_STANDARDDEV,reference_update_period,do_motion_smoothing,motion_smoothing_gain,default_nr_samples,DB_DEFAULT_CHUNK_SIZE,nr_corners,max_disparity,use_smaller_matching_window);
+      lut_x = db_AllocImage_f(w,h);
+      lut_y = db_AllocImage_f(w,h);
+
+    }
+
+    if ( color )
+    {
+      // save the color image:
+      color_ref = ref;
+    }
+
+    // make a grayscale image:
+    ref.ConvertToGray();
+
+    // compute the homography:
+    double H[9],Hinv[9];
+    db_Identity3x3(Hinv);
+    db_Identity3x3(H);
+
+    bool force_reference = false;
+
+#if PROFILE
+    gettimeofday(&ts1, NULL);
+#endif
+
+    reg.AddFrame(ref.GetRowPointers(),H,false,false);
+    cout << reg.profile_string << std::endl;
+
+#if PROFILE
+    gettimeofday(&ts2, NULL);
+
+    double elapsedTime = (ts2.tv_sec - ts1.tv_sec)*1000.0; // sec to ms
+    elapsedTime += (ts2.tv_usec - ts1.tv_usec)/1000.0; // us to ms
+    cout <<"\nelapsedTime for Reg<< "<<elapsedTime<<" ms >>>>>>>>>>>>>\n";
+#endif
+
+    if (frame_number == 0)
+    {
+      reg.UpdateReference(ref.GetRowPointers());
+    }
+
+
+    //std::vector<int> &inlier_indices = reg.GetInliers();
+    int *inlier_indices = reg.GetInliers();
+    int num_inlier_indices = reg.GetNrInliers();
+    printf("[%d] #Inliers = %d\n",frame_number,num_inlier_indices);
+
+    reg.Get_H_dref_to_ins(H);
+
+    db_GenerateHomographyLut(lut_x,lut_y,w,h,H);
+
+    // create a new image and warp:
+    PgmImage warped(w,h,format);
+
+#if PROFILE
+    gettimeofday(&ts3, NULL);
+#endif
+
+    if ( color )
+      db_WarpImageLutBilinear_rgb(color_ref.GetRowPointers(),warped.GetRowPointers(),w,h,lut_x,lut_y);
+    else
+      db_WarpImageLut_u(ref.GetRowPointers(),warped.GetRowPointers(),w,h,lut_x,lut_y,DB_WARP_FAST);
+
+#if PROFILE
+    gettimeofday(&ts4, NULL);
+    elapsedTime = (ts4.tv_sec - ts3.tv_sec)*1000.0; // sec to ms
+    elapsedTime += (ts4.tv_usec - ts3.tv_usec)/1000.0;     // us to ms
+    cout <<"\nelapsedTime for Warp <<"<<elapsedTime<<" ms >>>>>>>>>>>>>\n";
+#endif
+
+    // write aligned image: name is aligned_<corresponding input file name>
+    stringstream s;
+    s << "aligned_" << file_name;
+    warped.WritePGM(s.str());
+
+    /*
+    // Get the reference and inspection corners to write to file
+    double *ref_corners = reg.GetRefCorners();
+    double *ins_corners = reg.GetInsCorners();
+
+    // get the image file name (without extension), so we
+    // can generate the corresponding filenames for matches
+    // and inliers
+    string file_name_root(file_name.substr(0,file_name.rfind(".")));
+
+    // write matches to file
+    s.str(string(""));
+    s << "Matches_" << file_name_root << ".txt";
+
+    ofstream  match_file(s.str().c_str());
+
+    for (int i = 0; i < reg.GetNrMatches(); i++)
+    {
+      match_file << ref_corners[3*i] << " " << ref_corners[3*i+1] << " " << ins_corners[3*i] << " " << ins_corners[3*i+1] << endl;
+    }
+
+    match_file.close();
+
+    // write the inlier matches to file
+    s.str(string(""));
+    s << "InlierMatches_" << file_name_root << ".txt";
+
+    ofstream inlier_match_file(s.str().c_str());
+
+    for(int i=0; i<num_inlier_indices; i++)
+    {
+      int k = inlier_indices[i];
+      inlier_match_file << ref_corners[3*k] << " "
+            << ref_corners[3*k+1] << " "
+            << ins_corners[3*k] << " "
+            << ins_corners[3*k+1] << endl;
+    }
+    inlier_match_file.close();
+    */
+
+    frame_number++;
+  }
+
+  if ( reg.Initialized() )
+  {
+    db_FreeImage_f(lut_x,h);
+    db_FreeImage_f(lut_y,h);
+  }
+
+  return 0;
+}
+
+void parse_cmd_line(stringstream& cmdline,
+            const int argc,
+            const string& progname,
+            string& image_list_file_name,
+            int& nr_corners,
+            double& max_disparity,
+            int& motion_model_type,
+            bool& quarter_resolution,
+            unsigned int& reference_update_period,
+            bool& do_motion_smoothing,
+            double& motion_smoothing_gain)
+{
+  // for counting down the parsed arguments.
+  int c = argc;
+
+  // a holder
+  string token;
+
+  while (cmdline >> token)
+  {
+    --c;
+
+    int pos = token.find("-");
+
+    if (pos == 0)
+    {
+      switch (token[1])
+      {
+      case 'm':
+    --c; cmdline >> token;
+    if (token.compare("rt") == 0)
+    {
+      motion_model_type = DB_HOMOGRAPHY_TYPE_R_T;
+    }
+    else if (token.compare("a") == 0)
+    {
+      motion_model_type = DB_HOMOGRAPHY_TYPE_AFFINE;
+    }
+    else if (token.compare("p") == 0)
+    {
+      motion_model_type = DB_HOMOGRAPHY_TYPE_PROJECTIVE;
+    }
+    else
+    {
+      usage(progname);
+      exit(1);
+    }
+    break;
+      case 'c':
+    --c; cmdline >> nr_corners;
+    break;
+      case 'd':
+    --c; cmdline >> max_disparity;
+    break;
+      case 'q':
+    quarter_resolution = true;
+    break;
+      case 'r':
+    --c; cmdline >> reference_update_period;
+    break;
+      case 's':
+    --c; cmdline >> do_motion_smoothing;
+    break;
+      case 'g':
+    --c; cmdline >> motion_smoothing_gain;
+    break;
+      default:
+    cerr << progname << "illegal option " << token << endl;
+      case 'h':
+    usage(progname);
+    exit(1);
+    break;
+      }
+    }
+    else
+    {
+      if (c != 1)
+      {
+    usage(progname);
+    exit(1);
+      }
+      else
+      {
+    --c;
+    image_list_file_name = token;
+      }
+    }
+  }
+
+  if (c != 0)
+  {
+    usage(progname);
+    exit(1);
+  }
+}
+
diff --git a/jni_mosaic/feature_stab/src/dbregtest/stdafx.cpp b/jni_mosaic/feature_stab/src/dbregtest/stdafx.cpp
new file mode 100644
index 0000000..0c703e2
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/stdafx.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// stdafx.cpp : source file that includes just the standard includes
+// dbregtest.pch will be the pre-compiled header
+// stdafx.obj will contain the pre-compiled type information
+
+#include "stdafx.h"
+
+// TODO: reference any additional headers you need in STDAFX.H
+// and not in this file
diff --git a/jni_mosaic/feature_stab/src/dbregtest/stdafx.h b/jni_mosaic/feature_stab/src/dbregtest/stdafx.h
new file mode 100644
index 0000000..9bc06ea
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/stdafx.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#include <stdio.h>
+
+// TODO: reference additional headers your program requires here
diff --git a/jni_mosaic/feature_stab/src/dbregtest/targetver.h b/jni_mosaic/feature_stab/src/dbregtest/targetver.h
new file mode 100644
index 0000000..9272b0d
--- /dev/null
+++ b/jni_mosaic/feature_stab/src/dbregtest/targetver.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#pragma once
+
+// The following macros define the minimum required platform.  The minimum required platform
+// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run
+// your application.  The macros work by enabling all features available on platform versions up to and
+// including the version specified.
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef _WIN32_WINNT            // Specifies that the minimum required platform is Windows Vista.
+#define _WIN32_WINNT 0x0600     // Change this to the appropriate value to target other versions of Windows.
+#endif
+
diff --git a/jni_mosaic/mosaic_renderer_jni.cpp b/jni_mosaic/mosaic_renderer_jni.cpp
new file mode 100644
index 0000000..36f8064
--- /dev/null
+++ b/jni_mosaic/mosaic_renderer_jni.cpp
@@ -0,0 +1,804 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <jni.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "db_utilities_camera.h"
+#include "mosaic/ImageUtils.h"
+#include "mosaic_renderer/FrameBuffer.h"
+#include "mosaic_renderer/WarpRenderer.h"
+#include "mosaic_renderer/SurfaceTextureRenderer.h"
+#include "mosaic_renderer/YVURenderer.h"
+
+#include "mosaic/Log.h"
+#define LOG_TAG "MosaicRenderer"
+
+#include "mosaic_renderer_jni.h"
+
+// Texture handle
+GLuint gSurfaceTextureID[1];
+
+bool gWarpImage = true;
+
+// Low-Res input image frame in YUVA format for preview rendering and processing
+// and high-res YUVA input image for processing.
+unsigned char* gPreviewImage[NR];
+// Low-Res & high-res preview image width
+int gPreviewImageWidth[NR];
+// Low-Res & high-res preview image height
+int gPreviewImageHeight[NR];
+
+// Semaphore to protect simultaneous read/writes from gPreviewImage
+sem_t gPreviewImage_semaphore;
+
+// Off-screen preview FBO width (large enough to store the entire
+// preview mosaic). FBO is frame buffer object.
+int gPreviewFBOWidth;
+// Off-screen preview FBO height (large enough to store the entire
+// preview mosaic).
+int gPreviewFBOHeight;
+
+// gK is the transformation to map the canonical {-1,1} vertex coordinate system
+// to the {0,gPreviewImageWidth[LR]} input image frame coordinate system before
+// applying the given affine transformation trs. gKm is the corresponding
+// transformation for going to the {0,gPreviewFBOWidth}.
+double gK[9];
+double gKinv[9];
+double gKm[9];
+double gKminv[9];
+
+// Shader to copy input SurfaceTexture into and RGBA FBO. The two shaders
+// render to the textures with dimensions corresponding to the low-res and
+// high-res image frames.
+SurfaceTextureRenderer gSurfTexRenderer[NR];
+// Off-screen FBOs to store the low-res and high-res RGBA copied out from
+// the SurfaceTexture by the gSurfTexRenderers.
+FrameBuffer gBufferInput[NR];
+
+// Shader to convert RGBA textures into YVU textures for processing
+YVURenderer gYVURenderer[NR];
+// Off-screen FBOs to store the low-res and high-res YVU textures for processing
+FrameBuffer gBufferInputYVU[NR];
+
+// Shader to translate the flip-flop FBO - gBuffer[1-current] -> gBuffer[current]
+WarpRenderer gWarper1;
+// Shader to add warped current frame to the flip-flop FBO - gBuffer[current]
+WarpRenderer gWarper2;
+// Off-screen FBOs (flip-flop) to store the result of gWarper1 & gWarper2
+FrameBuffer gBuffer[2];
+
+// Shader to warp and render the preview FBO to the screen
+WarpRenderer gPreview;
+
+// Index of the gBuffer FBO gWarper1 is going to write into
+int gCurrentFBOIndex = 0;
+
+// 3x3 Matrices holding the transformation of this frame (gThisH1t) and of
+// the last frame (gLastH1t) w.r.t the first frame.
+double gThisH1t[9];
+double gLastH1t[9];
+
+// Variables to represent the fixed position of the top-left corner of the
+// current frame in the previewFBO
+double gCenterOffsetX = 0.0f;
+double gCenterOffsetY = 0.0f;
+
+// X-Offset of the viewfinder (current frame) w.r.t
+// (gCenterOffsetX, gCenterOffsetY). This offset varies with time and is
+// used to pan the viewfinder across the UI layout.
+double gPanOffset = 0.0f;
+
+// Variables tracking the translation value for the current frame and the
+// last frame (both w.r.t the first frame). The difference between these
+// values is used to control the panning speed of the viewfinder display
+// on the UI screen.
+double gThisTx = 0.0f;
+double gLastTx = 0.0f;
+
+// These are the scale factors used by the gPreview shader to ensure that
+// the image frame is correctly scaled to the full UI layout height while
+// maintaining its aspect ratio
+double gUILayoutScalingX = 1.0f;
+double gUILayoutScalingY = 1.0f;
+
+// Whether the view that we will render preview FBO onto is in landscape or portrait
+// orientation.
+bool gIsLandscapeOrientation = true;
+
+// State of the viewfinder. Set to false when the viewfinder hits the UI edge.
+bool gPanViewfinder = true;
+
+// Affine transformation in GL 4x4 format (column-major) to warp the
+// last frame mosaic into the current frame coordinate system.
+GLfloat g_dAffinetransGL[16];
+double g_dAffinetrans[16];
+
+// Affine transformation in GL 4x4 format (column-major) to translate the
+// preview FBO across the screen (viewfinder panning).
+GLfloat g_dAffinetransPanGL[16];
+double g_dAffinetransPan[16];
+
+// XY translation in GL 4x4 format (column-major) to center the current
+// preview mosaic in the preview FBO
+GLfloat g_dTranslationToFBOCenterGL[16];
+double g_dTranslationToFBOCenter[16];
+
+// GL 4x4 Identity transformation
+GLfloat g_dAffinetransIdentGL[] = {
+    1., 0., 0., 0.,
+    0., 1., 0., 0.,
+    0., 0., 1., 0.,
+    0., 0., 0., 1.};
+
+// GL 4x4 Rotation transformation (column-majored): 90 degree
+GLfloat g_dAffinetransRotation90GL[] = {
+    0., 1., 0., 0.,
+    -1., 0., 0., 0.,
+    0., 0., 1., 0.,
+    0., 0., 0., 1.};
+
+// 3x3 Rotation transformation (row-majored): 90 degree
+double gRotation90[] = {
+    0., -1., 0.,
+    1., 0., 0.,
+    0., 0., 1.,};
+
+
+float g_dIdent3x3[] = {
+    1.0, 0.0, 0.0,
+    0.0, 1.0, 0.0,
+    0.0, 0.0, 1.0};
+
+const int GL_TEXTURE_EXTERNAL_OES_ENUM = 0x8D65;
+
+static void printGLString(const char *name, GLenum s) {
+    const char *v = (const char *) glGetString(s);
+    LOGI("GL %s = %s", name, v);
+}
+
+void checkFramebufferStatus(const char* name) {
+    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+    if (status == 0) {
+      LOGE("Checking completeness of Framebuffer:%s", name);
+      checkGlError("checkFramebufferStatus (is the target \"GL_FRAMEBUFFER\"?)");
+    } else if (status != GL_FRAMEBUFFER_COMPLETE) {
+        const char* msg = "not listed";
+        switch (status) {
+          case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: msg = "attachment"; break;
+          case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: msg = "dimensions"; break;
+          case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: msg = "missing attachment"; break;
+          case GL_FRAMEBUFFER_UNSUPPORTED: msg = "unsupported"; break;
+        }
+        LOGE("Framebuffer: %s is INCOMPLETE: %s, %x", name, msg, status);
+    }
+}
+
+// @return false if there was an error
+bool checkGLErrorDetail(const char* file, int line, const char* op) {
+    GLint error = glGetError();
+    const char* err_msg = "NOT_LISTED";
+    if (error != 0) {
+        switch (error) {
+            case GL_INVALID_VALUE: err_msg = "NOT_LISTED_YET"; break;
+            case GL_INVALID_OPERATION: err_msg = "INVALID_OPERATION"; break;
+            case GL_INVALID_ENUM: err_msg = "INVALID_ENUM"; break;
+        }
+        LOGE("Error after %s(). glError: %s (0x%x) in line %d of %s", op, err_msg, error, line, file);
+        return false;
+    }
+    return true;
+}
+
+void bindSurfaceTexture(GLuint texId)
+{
+    glBindTexture(GL_TEXTURE_EXTERNAL_OES_ENUM, texId);
+
+    // Can't do mipmapping with camera source
+    glTexParameterf(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_MIN_FILTER,
+            GL_LINEAR);
+    glTexParameterf(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_MAG_FILTER,
+            GL_LINEAR);
+    // Clamp to edge is the only option
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_WRAP_S,
+            GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_EXTERNAL_OES_ENUM, GL_TEXTURE_WRAP_T,
+            GL_CLAMP_TO_EDGE);
+}
+
+void ClearPreviewImage(int mID)
+{
+    unsigned char* ptr = gPreviewImage[mID];
+    for(int j = 0, i = 0;
+            j < gPreviewImageWidth[mID] * gPreviewImageHeight[mID] * 4;
+            j += 4)
+    {
+            ptr[i++] = 0;
+            ptr[i++] = 0;
+            ptr[i++] = 0;
+            ptr[i++] = 255;
+    }
+
+}
+
+void ConvertAffine3x3toGL4x4(double *matGL44, double *mat33)
+{
+    matGL44[0] = mat33[0];
+    matGL44[1] = mat33[3];
+    matGL44[2] = 0.0;
+    matGL44[3] = mat33[6];
+
+    matGL44[4] = mat33[1];
+    matGL44[5] = mat33[4];
+    matGL44[6] = 0.0;
+    matGL44[7] = mat33[7];
+
+    matGL44[8] = 0;
+    matGL44[9] = 0;
+    matGL44[10] = 1.0;
+    matGL44[11] = 0.0;
+
+    matGL44[12] = mat33[2];
+    matGL44[13] = mat33[5];
+    matGL44[14] = 0.0;
+    matGL44[15] = mat33[8];
+}
+
+bool continuePanningFBO(double panOffset) {
+    double normalizedScreenLimitLeft = -1.0 + VIEWPORT_BORDER_FACTOR_HORZ * 2.0;
+    double normalizedScreenLimitRight = 1.0 - VIEWPORT_BORDER_FACTOR_HORZ * 2.0;
+    double normalizedXPositionOnScreenLeft;
+    double normalizedXPositionOnScreenRight;
+
+    // Compute the position of the current frame in the screen coordinate system
+    if (gIsLandscapeOrientation) {
+        normalizedXPositionOnScreenLeft = (2.0 *
+            (gCenterOffsetX + panOffset) / gPreviewFBOWidth - 1.0) *
+            gUILayoutScalingX;
+        normalizedXPositionOnScreenRight = (2.0 *
+            ((gCenterOffsetX + panOffset) + gPreviewImageWidth[HR]) /
+            gPreviewFBOWidth - 1.0) * gUILayoutScalingX;
+    } else {
+        normalizedXPositionOnScreenLeft = (2.0 *
+            (gCenterOffsetX + panOffset) / gPreviewFBOWidth - 1.0) *
+            gUILayoutScalingY;
+        normalizedXPositionOnScreenRight = (2.0 *
+            ((gCenterOffsetX + panOffset) + gPreviewImageWidth[HR]) /
+            gPreviewFBOWidth - 1.0) * gUILayoutScalingY;
+    }
+
+    // Stop the viewfinder panning if we hit the maximum border allowed for
+    // this UI layout
+    if (normalizedXPositionOnScreenRight > normalizedScreenLimitRight ||
+            normalizedXPositionOnScreenLeft < normalizedScreenLimitLeft) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+// This function computes fills the 4x4 matrices g_dAffinetrans,
+// and g_dAffinetransPan using the specified 3x3 affine
+// transformation between the first captured frame and the current frame.
+// The computed g_dAffinetrans is such that it warps the preview mosaic in
+// the last frame's coordinate system into the coordinate system of the
+// current frame. Thus, applying this transformation will create the current
+// frame mosaic but with the current frame missing. This frame will then be
+// pasted in by gWarper2 after translating it by g_dTranslationToFBOCenter.
+// The computed g_dAffinetransPan is such that it offsets the computed preview
+// mosaic horizontally to make the viewfinder pan within the UI layout.
+void UpdateWarpTransformation(float *trs)
+{
+    double H[9], Hp[9], Htemp1[9], Htemp2[9], T[9];
+
+    for(int i = 0; i < 9; i++)
+    {
+        gThisH1t[i] = trs[i];
+    }
+
+    // Alignment is done based on low-res data.
+    // To render the preview mosaic, the translation of the high-res mosaic is estimated to
+    // H2L_FACTOR x low-res-based tranlation.
+    gThisH1t[2] *= H2L_FACTOR;
+    gThisH1t[5] *= H2L_FACTOR;
+
+    db_Identity3x3(T);
+    T[2] = -gCenterOffsetX;
+    T[5] = -gCenterOffsetY;
+
+    // H = ( inv(gThisH1t) * gLastH1t ) * T
+    db_Identity3x3(Htemp1);
+    db_Identity3x3(Htemp2);
+    db_Identity3x3(H);
+    db_InvertAffineTransform(Htemp1, gThisH1t);
+    db_Multiply3x3_3x3(Htemp2, Htemp1, gLastH1t);
+    db_Multiply3x3_3x3(H, Htemp2, T);
+
+    for(int i = 0; i < 9; i++)
+    {
+        gLastH1t[i] = gThisH1t[i];
+    }
+
+    // Move the origin such that the frame is centered in the previewFBO
+    // i.e. H = inv(T) * H
+    H[2] += gCenterOffsetX;
+    H[5] += gCenterOffsetY;
+
+    // Hp = inv(Km) * H * Km
+    // Km moves the coordinate system from openGL to image pixels so
+    // that the alignment transform H can be applied to them.
+    // inv(Km) moves the coordinate system back to openGL normalized
+    // coordinates so that the shader can correctly render it.
+    db_Identity3x3(Htemp1);
+    db_Multiply3x3_3x3(Htemp1, H, gKm);
+    db_Multiply3x3_3x3(Hp, gKminv, Htemp1);
+
+    ConvertAffine3x3toGL4x4(g_dAffinetrans, Hp);
+
+    ////////////////////////////////////////////////
+    ////// Compute g_dAffinetransPan now...   //////
+    ////////////////////////////////////////////////
+
+    gThisTx = trs[2];
+
+    if(gPanViewfinder)
+    {
+        gPanOffset += (gThisTx - gLastTx) * VIEWFINDER_PAN_FACTOR_HORZ;
+    }
+
+    gLastTx = gThisTx;
+    gPanViewfinder = continuePanningFBO(gPanOffset);
+
+    db_Identity3x3(H);
+    H[2] = gPanOffset;
+
+    // Hp = inv(Km) * H * Km
+    db_Identity3x3(Htemp1);
+    db_Multiply3x3_3x3(Htemp1, H, gKm);
+    db_Multiply3x3_3x3(Hp, gKminv, Htemp1);
+
+    if (gIsLandscapeOrientation) {
+        ConvertAffine3x3toGL4x4(g_dAffinetransPan, Hp);
+    } else {
+        // rotate Hp by 90 degress.
+        db_Multiply3x3_3x3(Htemp1, gRotation90, Hp);
+        ConvertAffine3x3toGL4x4(g_dAffinetransPan, Htemp1);
+    }
+}
+
+void AllocateTextureMemory(int widthHR, int heightHR, int widthLR, int heightLR)
+{
+    gPreviewImageWidth[HR] = widthHR;
+    gPreviewImageHeight[HR] = heightHR;
+
+    gPreviewImageWidth[LR] = widthLR;
+    gPreviewImageHeight[LR] = heightLR;
+
+    sem_wait(&gPreviewImage_semaphore);
+    gPreviewImage[LR] = ImageUtils::allocateImage(gPreviewImageWidth[LR],
+            gPreviewImageHeight[LR], 4);
+    gPreviewImage[HR] = ImageUtils::allocateImage(gPreviewImageWidth[HR],
+            gPreviewImageHeight[HR], 4);
+    sem_post(&gPreviewImage_semaphore);
+
+    gPreviewFBOWidth = PREVIEW_FBO_WIDTH_SCALE * gPreviewImageWidth[HR];
+    gPreviewFBOHeight = PREVIEW_FBO_HEIGHT_SCALE * gPreviewImageHeight[HR];
+
+    // The origin is such that the current frame will sit with its center
+    // at the center of the previewFBO
+    gCenterOffsetX = (gPreviewFBOWidth / 2 - gPreviewImageWidth[HR] / 2);
+    gCenterOffsetY = (gPreviewFBOHeight / 2 - gPreviewImageHeight[HR] / 2);
+
+    gPanOffset = 0.0f;
+
+    db_Identity3x3(gThisH1t);
+    db_Identity3x3(gLastH1t);
+
+    gPanViewfinder = true;
+
+    int w = gPreviewImageWidth[HR];
+    int h = gPreviewImageHeight[HR];
+
+    int wm = gPreviewFBOWidth;
+    int hm = gPreviewFBOHeight;
+
+    // K is the transformation to map the canonical [-1,1] vertex coordinate
+    // system to the [0,w] image coordinate system before applying the given
+    // affine transformation trs.
+    gKm[0] = wm / 2.0 - 0.5;
+    gKm[1] = 0.0;
+    gKm[2] = wm / 2.0 - 0.5;
+    gKm[3] = 0.0;
+    gKm[4] = hm / 2.0 - 0.5;
+    gKm[5] = hm / 2.0 - 0.5;
+    gKm[6] = 0.0;
+    gKm[7] = 0.0;
+    gKm[8] = 1.0;
+
+    gK[0] = w / 2.0 - 0.5;
+    gK[1] = 0.0;
+    gK[2] = w / 2.0 - 0.5;
+    gK[3] = 0.0;
+    gK[4] = h / 2.0 - 0.5;
+    gK[5] = h / 2.0 - 0.5;
+    gK[6] = 0.0;
+    gK[7] = 0.0;
+    gK[8] = 1.0;
+
+    db_Identity3x3(gKinv);
+    db_InvertCalibrationMatrix(gKinv, gK);
+
+    db_Identity3x3(gKminv);
+    db_InvertCalibrationMatrix(gKminv, gKm);
+
+    //////////////////////////////////////////
+    ////// Compute g_Translation now... //////
+    //////////////////////////////////////////
+    double T[9], Tp[9], Ttemp[9];
+
+    db_Identity3x3(T);
+    T[2] = gCenterOffsetX;
+    T[5] = gCenterOffsetY;
+
+    // Tp = inv(K) * T * K
+    db_Identity3x3(Ttemp);
+    db_Multiply3x3_3x3(Ttemp, T, gK);
+    db_Multiply3x3_3x3(Tp, gKinv, Ttemp);
+
+    ConvertAffine3x3toGL4x4(g_dTranslationToFBOCenter, Tp);
+
+    UpdateWarpTransformation(g_dIdent3x3);
+}
+
+void FreeTextureMemory()
+{
+    sem_wait(&gPreviewImage_semaphore);
+    ImageUtils::freeImage(gPreviewImage[LR]);
+    ImageUtils::freeImage(gPreviewImage[HR]);
+    sem_post(&gPreviewImage_semaphore);
+}
+
+extern "C"
+{
+    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
+    JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved);
+    JNIEXPORT jint JNICALL Java_com_android_camera_MosaicRenderer_init(
+            JNIEnv * env, jobject obj);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_reset(
+            JNIEnv * env, jobject obj,  jint width, jint height,
+            jboolean isLandscapeOrientation);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_preprocess(
+            JNIEnv * env, jobject obj, jfloatArray stMatrix);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_transferGPUtoCPU(
+            JNIEnv * env, jobject obj);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_step(
+            JNIEnv * env, jobject obj);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_updateMatrix(
+            JNIEnv * env, jobject obj);
+    JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_setWarping(
+            JNIEnv * env, jobject obj, jboolean flag);
+};
+
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+    sem_init(&gPreviewImage_semaphore, 0, 1);
+
+    return JNI_VERSION_1_4;
+}
+
+
+JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved)
+{
+    sem_destroy(&gPreviewImage_semaphore);
+}
+JNIEXPORT jint JNICALL Java_com_android_camera_MosaicRenderer_init(
+        JNIEnv * env, jobject obj)
+{
+    gSurfTexRenderer[LR].InitializeGLProgram();
+    gSurfTexRenderer[HR].InitializeGLProgram();
+    gYVURenderer[LR].InitializeGLProgram();
+    gYVURenderer[HR].InitializeGLProgram();
+    gWarper1.InitializeGLProgram();
+    gWarper2.InitializeGLProgram();
+    gPreview.InitializeGLProgram();
+    gBuffer[0].InitializeGLContext();
+    gBuffer[1].InitializeGLContext();
+    gBufferInput[LR].InitializeGLContext();
+    gBufferInput[HR].InitializeGLContext();
+    gBufferInputYVU[LR].InitializeGLContext();
+    gBufferInputYVU[HR].InitializeGLContext();
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    glGenTextures(1, gSurfaceTextureID);
+    // bind the surface texture
+    bindSurfaceTexture(gSurfaceTextureID[0]);
+
+    return (jint) gSurfaceTextureID[0];
+}
+
+// width: the width of the view
+// height: the height of the view
+// isLandscape: whether the device is in landscape or portrait. Android
+//     Compatibility Definition Document specifies that the long side of the
+//     camera aligns with the long side of the screen.
+void calculateUILayoutScaling(int width, int height, bool isLandscape) {
+    if (isLandscape) {
+        //  __________        ________
+        // |          |  =>  |________|
+        // |__________|  =>    (View)
+        // (Preview FBO)
+        //
+        // Scale the preview FBO's height to the height of view and
+        // maintain the aspect ratio of the current frame on the screen.
+        gUILayoutScalingY = PREVIEW_FBO_HEIGHT_SCALE;
+
+        // Note that OpenGL scales a texture to view's width and height automatically.
+        // The "width / height" inverts the scaling, so as to maintain the aspect ratio
+        // of the current frame.
+        gUILayoutScalingX = ((float) gPreviewFBOWidth / gPreviewFBOHeight)
+                / ((float) width / height) * PREVIEW_FBO_HEIGHT_SCALE;
+    } else {
+        //                   ___
+        //  __________      |   |     ______
+        // |          |  => |   | => |______|
+        // |__________|  => |   | =>  (View)
+        // (Preview FBO)    |   |
+        //                  |___|
+        //
+        // Scale the preview FBO's height to the width of view and
+        // maintain the aspect ratio of the current frame on the screen.
+        // In preview, Java_com_android_camera_MosaicRenderer_step rotates the
+        // preview FBO by 90 degrees. In capture, UpdateWarpTransformation
+        // rotates the preview FBO.
+        gUILayoutScalingY = PREVIEW_FBO_WIDTH_SCALE;
+
+        // Note that OpenGL scales a texture to view's width and height automatically.
+        // The "height / width" inverts the scaling, so as to maintain the aspect ratio
+        // of the current frame.
+        gUILayoutScalingX = ((float) gPreviewFBOHeight / gPreviewFBOWidth)
+                / ((float) width / height) * PREVIEW_FBO_WIDTH_SCALE;
+    }
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_reset(
+        JNIEnv * env, jobject obj,  jint width, jint height, jboolean isLandscapeOrientation)
+{
+    gIsLandscapeOrientation = isLandscapeOrientation;
+    calculateUILayoutScaling(width, height, gIsLandscapeOrientation);
+
+    gBuffer[0].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);
+    gBuffer[1].Init(gPreviewFBOWidth, gPreviewFBOHeight, GL_RGBA);
+
+    gBufferInput[LR].Init(gPreviewImageWidth[LR],
+            gPreviewImageHeight[LR], GL_RGBA);
+
+    gBufferInput[HR].Init(gPreviewImageWidth[HR],
+            gPreviewImageHeight[HR], GL_RGBA);
+
+    gBufferInputYVU[LR].Init(gPreviewImageWidth[LR],
+            gPreviewImageHeight[LR], GL_RGBA);
+
+    gBufferInputYVU[HR].Init(gPreviewImageWidth[HR],
+            gPreviewImageHeight[HR], GL_RGBA);
+
+    // bind the surface texture
+    bindSurfaceTexture(gSurfaceTextureID[0]);
+
+    // To speed up, there is no need to clear the destination buffers
+    // (offscreen/screen buffers) of gSurfTexRenderer, gYVURenderer
+    // and gPreview because we always fill the whole destination buffers
+    // when we draw something to those offscreen/screen buffers.
+    gSurfTexRenderer[LR].SetupGraphics(&gBufferInput[LR]);
+    gSurfTexRenderer[LR].SetViewportMatrix(1, 1, 1, 1);
+    gSurfTexRenderer[LR].SetScalingMatrix(1.0f, -1.0f);
+    gSurfTexRenderer[LR].SetInputTextureName(gSurfaceTextureID[0]);
+    gSurfTexRenderer[LR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);
+
+    gSurfTexRenderer[HR].SetupGraphics(&gBufferInput[HR]);
+    gSurfTexRenderer[HR].SetViewportMatrix(1, 1, 1, 1);
+    gSurfTexRenderer[HR].SetScalingMatrix(1.0f, -1.0f);
+    gSurfTexRenderer[HR].SetInputTextureName(gSurfaceTextureID[0]);
+    gSurfTexRenderer[HR].SetInputTextureType(GL_TEXTURE_EXTERNAL_OES_ENUM);
+
+    gYVURenderer[LR].SetupGraphics(&gBufferInputYVU[LR]);
+    gYVURenderer[LR].SetInputTextureName(gBufferInput[LR].GetTextureName());
+    gYVURenderer[LR].SetInputTextureType(GL_TEXTURE_2D);
+
+    gYVURenderer[HR].SetupGraphics(&gBufferInputYVU[HR]);
+    gYVURenderer[HR].SetInputTextureName(gBufferInput[HR].GetTextureName());
+    gYVURenderer[HR].SetInputTextureType(GL_TEXTURE_2D);
+
+    // gBuffer[1-gCurrentFBOIndex] --> gWarper1 --> gBuffer[gCurrentFBOIndex]
+    gWarper1.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
+
+    // Clear the destination buffer of gWarper1.
+    gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
+    gWarper1.SetViewportMatrix(1, 1, 1, 1);
+    gWarper1.SetScalingMatrix(1.0f, 1.0f);
+    gWarper1.SetInputTextureName(gBuffer[1 - gCurrentFBOIndex].GetTextureName());
+    gWarper1.SetInputTextureType(GL_TEXTURE_2D);
+
+    // gBufferInput[HR] --> gWarper2 --> gBuffer[gCurrentFBOIndex]
+    gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
+
+    // gWarp2's destination buffer is the same to gWarp1's. No need to clear it
+    // again.
+    gWarper2.SetViewportMatrix(gPreviewImageWidth[HR],
+            gPreviewImageHeight[HR], gBuffer[gCurrentFBOIndex].GetWidth(),
+            gBuffer[gCurrentFBOIndex].GetHeight());
+    gWarper2.SetScalingMatrix(1.0f, 1.0f);
+    gWarper2.SetInputTextureName(gBufferInput[HR].GetTextureName());
+    gWarper2.SetInputTextureType(GL_TEXTURE_2D);
+
+    // gBuffer[gCurrentFBOIndex] --> gPreview --> Screen
+    gPreview.SetupGraphics(width, height);
+    gPreview.SetViewportMatrix(1, 1, 1, 1);
+
+    // Scale the previewFBO so that the viewfinder window fills the layout height
+    // while maintaining the image aspect ratio
+    gPreview.SetScalingMatrix(gUILayoutScalingX, -1.0f * gUILayoutScalingY);
+    gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());
+    gPreview.SetInputTextureType(GL_TEXTURE_2D);
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_preprocess(
+        JNIEnv * env, jobject obj, jfloatArray stMatrix)
+{
+    jfloat *stmat = env->GetFloatArrayElements(stMatrix, 0);
+
+    gSurfTexRenderer[LR].SetSTMatrix((float*) stmat);
+    gSurfTexRenderer[HR].SetSTMatrix((float*) stmat);
+
+    env->ReleaseFloatArrayElements(stMatrix, stmat, 0);
+
+    gSurfTexRenderer[LR].DrawTexture(g_dAffinetransIdentGL);
+    gSurfTexRenderer[HR].DrawTexture(g_dAffinetransIdentGL);
+}
+
+#ifndef now_ms
+#include <time.h>
+static double
+now_ms(void)
+{
+    //struct timespec res;
+    struct timeval res;
+    //clock_gettime(CLOCK_REALTIME, &res);
+    gettimeofday(&res, NULL);
+    return 1000.0*res.tv_sec + (double)res.tv_usec/1e3;
+}
+#endif
+
+
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_transferGPUtoCPU(
+        JNIEnv * env, jobject obj)
+{
+    double t0, t1, time_c;
+
+    gYVURenderer[LR].DrawTexture();
+    gYVURenderer[HR].DrawTexture();
+
+    sem_wait(&gPreviewImage_semaphore);
+    // Bind to the input LR FBO and read the Low-Res data from there...
+    glBindFramebuffer(GL_FRAMEBUFFER, gBufferInputYVU[LR].GetFrameBufferName());
+    t0 = now_ms();
+    glReadPixels(0,
+                 0,
+                 gBufferInput[LR].GetWidth(),
+                 gBufferInput[LR].GetHeight(),
+                 GL_RGBA,
+                 GL_UNSIGNED_BYTE,
+                 gPreviewImage[LR]);
+
+    checkGlError("glReadPixels LR (MosaicRenderer.transferGPUtoCPU())");
+
+    // Bind to the input HR FBO and read the high-res data from there...
+    glBindFramebuffer(GL_FRAMEBUFFER, gBufferInputYVU[HR].GetFrameBufferName());
+    t0 = now_ms();
+    glReadPixels(0,
+                 0,
+                 gBufferInput[HR].GetWidth(),
+                 gBufferInput[HR].GetHeight(),
+                 GL_RGBA,
+                 GL_UNSIGNED_BYTE,
+                 gPreviewImage[HR]);
+
+    checkGlError("glReadPixels HR (MosaicRenderer.transferGPUtoCPU())");
+
+    sem_post(&gPreviewImage_semaphore);
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_step(
+        JNIEnv * env, jobject obj)
+{
+    if(!gWarpImage) // ViewFinder
+    {
+        gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
+        gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());
+
+        gWarper2.DrawTexture(g_dTranslationToFBOCenterGL);
+
+        if (gIsLandscapeOrientation) {
+            gPreview.DrawTexture(g_dAffinetransIdentGL);
+        } else {
+            gPreview.DrawTexture(g_dAffinetransRotation90GL);
+        }
+    }
+    else
+    {
+        gWarper1.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
+        // Clear the destination so that we can paint on it afresh
+        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
+        gWarper1.SetInputTextureName(
+                gBuffer[1 - gCurrentFBOIndex].GetTextureName());
+        gWarper2.SetupGraphics(&gBuffer[gCurrentFBOIndex]);
+        gPreview.SetInputTextureName(gBuffer[gCurrentFBOIndex].GetTextureName());
+
+        gWarper1.DrawTexture(g_dAffinetransGL);
+        gWarper2.DrawTexture(g_dTranslationToFBOCenterGL);
+        gPreview.DrawTexture(g_dAffinetransPanGL);
+
+        gCurrentFBOIndex = 1 - gCurrentFBOIndex;
+    }
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_setWarping(
+        JNIEnv * env, jobject obj, jboolean flag)
+{
+    // TODO: Review this logic
+    if(gWarpImage != (bool) flag) //switching from viewfinder to capture or vice-versa
+    {
+        // Clear gBuffer[0]
+        gWarper1.SetupGraphics(&gBuffer[0]);
+        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
+        // Clear gBuffer[1]
+        gWarper1.SetupGraphics(&gBuffer[1]);
+        gWarper1.Clear(0.0, 0.0, 0.0, 1.0);
+        // Clear the screen to black.
+        gPreview.Clear(0.0, 0.0, 0.0, 1.0);
+
+        gLastTx = 0.0f;
+        gPanOffset = 0.0f;
+        gPanViewfinder = true;
+
+        db_Identity3x3(gThisH1t);
+        db_Identity3x3(gLastH1t);
+        // Make sure g_dAffinetransGL and g_dAffinetransPanGL are updated.
+        // Otherwise, the first frame after setting the flag to true will be
+        // incorrectly drawn.
+        if ((bool) flag) {
+            UpdateWarpTransformation(g_dIdent3x3);
+        }
+    }
+
+    gWarpImage = (bool)flag;
+}
+
+JNIEXPORT void JNICALL Java_com_android_camera_MosaicRenderer_updateMatrix(
+        JNIEnv * env, jobject obj)
+{
+    for(int i=0; i<16; i++)
+    {
+        g_dAffinetransGL[i] = g_dAffinetrans[i];
+        g_dAffinetransPanGL[i] = g_dAffinetransPan[i];
+        g_dTranslationToFBOCenterGL[i] = g_dTranslationToFBOCenter[i];
+    }
+}
diff --git a/jni_mosaic/mosaic_renderer_jni.h b/jni_mosaic/mosaic_renderer_jni.h
new file mode 100644
index 0000000..7a1b878
--- /dev/null
+++ b/jni_mosaic/mosaic_renderer_jni.h
@@ -0,0 +1,35 @@
+#pragma once
+#include <semaphore.h>
+
+// The Preview FBO dimensions are determined from the high-res
+// frame dimensions (gPreviewImageWidth, gPreviewImageHeight)
+// using the scale factors below.
+const int PREVIEW_FBO_WIDTH_SCALE = 2;
+const int PREVIEW_FBO_HEIGHT_SCALE = 2;
+
+// The factor below determines the (horizontal) speed at which the viewfinder
+// will pan across the UI during capture. A value of 0.0 will keep the viewfinder
+// static in the center of the screen and 1.0f will make it pan at the
+// same speed as the device.
+const float VIEWFINDER_PAN_FACTOR_HORZ = 0.0f;
+
+// What fraction of the screen viewport width has been allocated to show the
+// arrows on the direction of motion side.
+const float VIEWPORT_BORDER_FACTOR_HORZ = 0.1f;
+
+const int LR = 0; // Low-resolution mode
+const int HR = 1; // High-resolution mode
+const int NR = 2; // Number of resolution modes
+
+const int H2L_FACTOR = 4; // Can be 2
+
+extern "C" void AllocateTextureMemory(int widthHR, int heightHR,
+        int widthLR, int heightLR);
+extern "C" void FreeTextureMemory();
+extern "C" void UpdateWarpTransformation(float *trs);
+
+extern unsigned char* gPreviewImage[NR];
+extern int gPreviewImageWidth[NR];
+extern int gPreviewImageHeight[NR];
+
+extern sem_t gPreviewImage_semaphore;
diff --git a/perftests/panorama/Android.mk b/perftests/panorama/Android.mk
new file mode 100755
index 0000000..17632df
--- /dev/null
+++ b/perftests/panorama/Android.mk
@@ -0,0 +1,26 @@
+local_target_dir := $(TARGET_OUT_DATA)/local/tmp
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH)/../../jni_mosaic/feature_mos/src \
+    $(LOCAL_PATH)/../../jni_mosaic/feature_stab/src \
+    $(LOCAL_PATH)/../../jni_mosaic/feature_stab/db_vlvm
+
+LOCAL_CFLAGS := -O3 -DNDEBUG
+
+LOCAL_SRC_FILES := benchmark.cpp
+
+LOCAL_SHARED_LIBRARIES := libjni_mosaic libGLESv2 libEGL
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_LDFLAGS :=  -llog -lGLESv2
+
+LOCAL_MODULE := panorama_bench
+
+LOCAL_MODULE_PATH := $(local_target_dir)
+
+include $(BUILD_EXECUTABLE)
diff --git a/perftests/panorama/README.txt b/perftests/panorama/README.txt
new file mode 100644
index 0000000..cbfb35a
--- /dev/null
+++ b/perftests/panorama/README.txt
@@ -0,0 +1,27 @@
+How to run and verify the benchmark:
+
+1) adb push input /data/panorama_input
+2) adb shell panorama_bench /data/panorama_input/test /data/panorama.ppm
+
+Sample output:
+
+38 frames loaded
+Iteration 0: 1454x330 moasic created: 4.33 seconds (2.05 + 2.28)
+Iteration 1: 1454x330 moasic created: 4.26 seconds (1.83 + 2.44)
+Iteration 2: 1454x330 moasic created: 5.57 seconds (2.73 + 2.84)
+Iteration 3: 1454x330 moasic created: 5.15 seconds (2.33 + 2.82)
+Iteration 4: 1454x330 moasic created: 6.22 seconds (2.05 + 4.16)
+Iteration 5: 1454x330 moasic created: 6.31 seconds (2.16 + 4.15)
+Iteration 6: 1454x330 moasic created: 5.04 seconds (2.03 + 3.01)
+Iteration 7: 1454x330 moasic created: 6.30 seconds (3.47 + 2.83)
+Iteration 8: 1454x330 moasic created: 6.57 seconds (1.83 + 4.73)
+Iteration 9: 1454x330 moasic created: 6.27 seconds (2.28 + 4.00)
+Total elapsed time: 56.02 seconds
+
+The first number in the parenthesis is the time to align the frames; the second
+number is the time to stitch them.
+
+The total elapsed time is the interesting number for benchmarking.
+
+3) adb pull /data/panorama.ppm .
+4) diff panorama.ppm output/golden.ppm
diff --git a/perftests/panorama/benchmark.cpp b/perftests/panorama/benchmark.cpp
new file mode 100755
index 0000000..2a6440f
--- /dev/null
+++ b/perftests/panorama/benchmark.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mosaic/Mosaic.h"
+#include "mosaic/ImageUtils.h"
+
+#define MAX_FRAMES 200
+#define KERNEL_ITERATIONS 10
+
+const int blendingType = Blend::BLEND_TYPE_HORZ;
+const int stripType = Blend::STRIP_TYPE_WIDE;
+
+ImageType yvuFrames[MAX_FRAMES];
+
+int loadImages(const char* basename, int &width, int &height)
+{
+    char filename[512];
+    struct stat filestat;
+    int i;
+
+    for (i = 0; i < MAX_FRAMES; i++) {
+        sprintf(filename, "%s_%03d.ppm", basename, i + 1);
+        if (stat(filename, &filestat) != 0) break;
+        ImageType rgbFrame = ImageUtils::readBinaryPPM(filename, width, height);
+        yvuFrames[i] = ImageUtils::allocateImage(width, height,
+                                ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+        ImageUtils::rgb2yvu(yvuFrames[i], rgbFrame, width, height);
+        ImageUtils::freeImage(rgbFrame);
+    }
+    return i;
+}
+
+int main(int argc, char **argv)
+{
+    struct timespec t1, t2, t3;
+
+    int width, height;
+    float totalElapsedTime = 0;
+
+    const char *basename;
+    const char *filename;
+
+    if (argc != 3) {
+        printf("Usage: %s input_dir output_filename\n", argv[0]);
+        return 0;
+    } else {
+        basename = argv[1];
+        filename = argv[2];
+    }
+
+    // Load the images outside the computational kernel
+    int totalFrames = loadImages(basename, width, height);
+
+    if (totalFrames == 0) {
+        printf("Image files not found. Make sure %s exists.\n",
+               basename);
+        return 1;
+    }
+
+    printf("%d frames loaded\n", totalFrames);
+
+
+    // Interesting stuff is here
+    for (int iteration = 0; iteration < KERNEL_ITERATIONS; iteration++)  {
+        Mosaic mosaic;
+
+        mosaic.initialize(blendingType, stripType, width, height, -1, false, 0);
+
+        clock_gettime(CLOCK_MONOTONIC, &t1);
+        for (int i = 0; i < totalFrames; i++) {
+            mosaic.addFrame(yvuFrames[i]);
+        }
+        clock_gettime(CLOCK_MONOTONIC, &t2);
+
+        float progress = 0.0;
+        bool cancelComputation = false;
+
+        mosaic.createMosaic(progress, cancelComputation);
+
+        int mosaicWidth, mosaicHeight;
+        ImageType resultYVU = mosaic.getMosaic(mosaicWidth, mosaicHeight);
+
+        ImageType imageRGB = ImageUtils::allocateImage(
+            mosaicWidth, mosaicHeight, ImageUtils::IMAGE_TYPE_NUM_CHANNELS);
+
+        clock_gettime(CLOCK_MONOTONIC, &t3);
+
+        float elapsedTime =
+            (t3.tv_sec - t1.tv_sec) + (t3.tv_nsec - t1.tv_nsec)/1e9;
+        float addImageTime =
+            (t2.tv_sec - t1.tv_sec) + (t2.tv_nsec - t1.tv_nsec)/1e9;
+        float stitchImageTime =
+            (t3.tv_sec - t2.tv_sec) + (t3.tv_nsec - t2.tv_nsec)/1e9;
+
+        totalElapsedTime += elapsedTime;
+
+        printf("Iteration %d: %dx%d moasic created: "
+               "%.2f seconds (%.2f + %.2f)\n",
+               iteration, mosaicWidth, mosaicHeight,
+               elapsedTime, addImageTime, stitchImageTime);
+
+        // Write the output only once for correctness check
+        if (iteration == 0) {
+            ImageUtils::yvu2rgb(imageRGB, resultYVU, mosaicWidth,
+                                mosaicHeight);
+            ImageUtils::writeBinaryPPM(imageRGB, filename, mosaicWidth,
+                                       mosaicHeight);
+        }
+    }
+    printf("Total elapsed time: %.2f seconds\n", totalElapsedTime);
+
+    return 0;
+}
diff --git a/perftests/panorama/input/test_001.ppm b/perftests/panorama/input/test_001.ppm
new file mode 100644
index 0000000..e7218bf
--- /dev/null
+++ b/perftests/panorama/input/test_001.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_002.ppm b/perftests/panorama/input/test_002.ppm
new file mode 100644
index 0000000..8975073
--- /dev/null
+++ b/perftests/panorama/input/test_002.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_003.ppm b/perftests/panorama/input/test_003.ppm
new file mode 100644
index 0000000..58c9e34
--- /dev/null
+++ b/perftests/panorama/input/test_003.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_004.ppm b/perftests/panorama/input/test_004.ppm
new file mode 100644
index 0000000..142c76b
--- /dev/null
+++ b/perftests/panorama/input/test_004.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_005.ppm b/perftests/panorama/input/test_005.ppm
new file mode 100644
index 0000000..ff229d3
--- /dev/null
+++ b/perftests/panorama/input/test_005.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_006.ppm b/perftests/panorama/input/test_006.ppm
new file mode 100644
index 0000000..2fc5c09
--- /dev/null
+++ b/perftests/panorama/input/test_006.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_007.ppm b/perftests/panorama/input/test_007.ppm
new file mode 100644
index 0000000..d7f6a9a
--- /dev/null
+++ b/perftests/panorama/input/test_007.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_008.ppm b/perftests/panorama/input/test_008.ppm
new file mode 100644
index 0000000..86d92b3
--- /dev/null
+++ b/perftests/panorama/input/test_008.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_009.ppm b/perftests/panorama/input/test_009.ppm
new file mode 100644
index 0000000..72dd05f
--- /dev/null
+++ b/perftests/panorama/input/test_009.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_010.ppm b/perftests/panorama/input/test_010.ppm
new file mode 100644
index 0000000..a09a054
--- /dev/null
+++ b/perftests/panorama/input/test_010.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_011.ppm b/perftests/panorama/input/test_011.ppm
new file mode 100644
index 0000000..be7b61b
--- /dev/null
+++ b/perftests/panorama/input/test_011.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_012.ppm b/perftests/panorama/input/test_012.ppm
new file mode 100644
index 0000000..67fad4a
--- /dev/null
+++ b/perftests/panorama/input/test_012.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_013.ppm b/perftests/panorama/input/test_013.ppm
new file mode 100644
index 0000000..6d92fd1
--- /dev/null
+++ b/perftests/panorama/input/test_013.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_014.ppm b/perftests/panorama/input/test_014.ppm
new file mode 100644
index 0000000..97aff41
--- /dev/null
+++ b/perftests/panorama/input/test_014.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_015.ppm b/perftests/panorama/input/test_015.ppm
new file mode 100644
index 0000000..d1de251
--- /dev/null
+++ b/perftests/panorama/input/test_015.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_016.ppm b/perftests/panorama/input/test_016.ppm
new file mode 100644
index 0000000..70ea1f5
--- /dev/null
+++ b/perftests/panorama/input/test_016.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_017.ppm b/perftests/panorama/input/test_017.ppm
new file mode 100644
index 0000000..e075c9e
--- /dev/null
+++ b/perftests/panorama/input/test_017.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_018.ppm b/perftests/panorama/input/test_018.ppm
new file mode 100644
index 0000000..adf023b
--- /dev/null
+++ b/perftests/panorama/input/test_018.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_019.ppm b/perftests/panorama/input/test_019.ppm
new file mode 100644
index 0000000..1f27d1d
--- /dev/null
+++ b/perftests/panorama/input/test_019.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_020.ppm b/perftests/panorama/input/test_020.ppm
new file mode 100644
index 0000000..fb95f52
--- /dev/null
+++ b/perftests/panorama/input/test_020.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_021.ppm b/perftests/panorama/input/test_021.ppm
new file mode 100644
index 0000000..43baadf
--- /dev/null
+++ b/perftests/panorama/input/test_021.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_022.ppm b/perftests/panorama/input/test_022.ppm
new file mode 100644
index 0000000..f928c83
--- /dev/null
+++ b/perftests/panorama/input/test_022.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_023.ppm b/perftests/panorama/input/test_023.ppm
new file mode 100644
index 0000000..e21b275
--- /dev/null
+++ b/perftests/panorama/input/test_023.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_024.ppm b/perftests/panorama/input/test_024.ppm
new file mode 100644
index 0000000..43ba0ba
--- /dev/null
+++ b/perftests/panorama/input/test_024.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_025.ppm b/perftests/panorama/input/test_025.ppm
new file mode 100644
index 0000000..b9f8892
--- /dev/null
+++ b/perftests/panorama/input/test_025.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_026.ppm b/perftests/panorama/input/test_026.ppm
new file mode 100644
index 0000000..201615f
--- /dev/null
+++ b/perftests/panorama/input/test_026.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_027.ppm b/perftests/panorama/input/test_027.ppm
new file mode 100644
index 0000000..07cf426
--- /dev/null
+++ b/perftests/panorama/input/test_027.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_028.ppm b/perftests/panorama/input/test_028.ppm
new file mode 100644
index 0000000..aedb023
--- /dev/null
+++ b/perftests/panorama/input/test_028.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_029.ppm b/perftests/panorama/input/test_029.ppm
new file mode 100644
index 0000000..9a0d398
--- /dev/null
+++ b/perftests/panorama/input/test_029.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_030.ppm b/perftests/panorama/input/test_030.ppm
new file mode 100644
index 0000000..26a8f53
--- /dev/null
+++ b/perftests/panorama/input/test_030.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_031.ppm b/perftests/panorama/input/test_031.ppm
new file mode 100644
index 0000000..2300461
--- /dev/null
+++ b/perftests/panorama/input/test_031.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_032.ppm b/perftests/panorama/input/test_032.ppm
new file mode 100644
index 0000000..f5e93f8
--- /dev/null
+++ b/perftests/panorama/input/test_032.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_033.ppm b/perftests/panorama/input/test_033.ppm
new file mode 100644
index 0000000..c2f8ad9
--- /dev/null
+++ b/perftests/panorama/input/test_033.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_034.ppm b/perftests/panorama/input/test_034.ppm
new file mode 100644
index 0000000..de93b23
--- /dev/null
+++ b/perftests/panorama/input/test_034.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_035.ppm b/perftests/panorama/input/test_035.ppm
new file mode 100644
index 0000000..62198de
--- /dev/null
+++ b/perftests/panorama/input/test_035.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_036.ppm b/perftests/panorama/input/test_036.ppm
new file mode 100644
index 0000000..bf252e4
--- /dev/null
+++ b/perftests/panorama/input/test_036.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_037.ppm b/perftests/panorama/input/test_037.ppm
new file mode 100644
index 0000000..7cc7ace
--- /dev/null
+++ b/perftests/panorama/input/test_037.ppm
Binary files differ
diff --git a/perftests/panorama/input/test_038.ppm b/perftests/panorama/input/test_038.ppm
new file mode 100644
index 0000000..d44e1f1
--- /dev/null
+++ b/perftests/panorama/input/test_038.ppm
Binary files differ
diff --git a/perftests/panorama/output/golden.ppm b/perftests/panorama/output/golden.ppm
new file mode 100644
index 0000000..5210933
--- /dev/null
+++ b/perftests/panorama/output/golden.ppm
Binary files differ
diff --git a/proguard.flags b/proguard.flags
index 8d60103..8cb0486 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -45,6 +45,8 @@
 # Disable the warnings of using dynamic method calls in EffectsRecorder
 -dontnote com.android.camera.EffectsRecorder
 
+-keep class android.support.v8.renderscript.** { *; }
+
 # Required for ActionBarSherlock
 -keep class android.support.v4.app.** { *; }
 -keep interface android.support.v4.app.** { *; }
@@ -55,3 +57,30 @@
 # Required for mp4parser
 -keep public class * implements com.coremedia.iso.boxes.Box
 
+#-assumenosideeffects junit.framework.Assert {
+#*;
+#}
+
+# For unit testing:
+
+# - Required for running exif tests on userdebug
+-keep class com.android.gallery3d.exif.ExifTag { *; }
+-keep class com.android.gallery3d.exif.ExifData { *; }
+-keep class com.android.gallery3d.exif.ExifInterface { *; }
+-keepclassmembers class com.android.gallery3d.exif.Util {
+  *** closeSilently(...);
+}
+
+# - Required for running blobcache tests on userdebug
+-keep class com.android.gallery3d.common.BlobCache { *; }
+
+# - Required for running glcanvas tests on userdebug
+-keep class com.android.gallery3d.ui.GLPaint { *; }
+-keep class com.android.gallery3d.ui.GLCanvas { *; }
+-keep class com.android.gallery3d.glrenderer.GLPaint { *; }
+-keep class com.android.gallery3d.glrenderer.GLCanvas { *; }
+-keep class com.android.gallery3d.ui.GLView { *; }
+-keepclassmembers class com.android.gallery3d.util.IntArray {
+  *** toArray(...);
+}
+-keep class com.android.gallery3d.util.ProfileData { *; }
diff --git a/res/anim/count_down_exit.xml b/res/anim/count_down_exit.xml
new file mode 100644
index 0000000..0091c5b
--- /dev/null
+++ b/res/anim/count_down_exit.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+        <alpha
+            android:fromAlpha="1.0"
+            android:toAlpha="0.0"
+            android:duration="1000" />
+        <scale
+            android:fromXScale="1.0"
+            android:fromYScale="1.0"
+            android:toXScale="3.0"
+            android:toYScale="3.0"
+            android:pivotX="50%"
+            android:pivotY="50%"
+            android:duration="800" />
+</set>
\ No newline at end of file
diff --git a/res/anim/on_screen_hint_enter.xml b/res/anim/on_screen_hint_enter.xml
new file mode 100644
index 0000000..91653a2
--- /dev/null
+++ b/res/anim/on_screen_hint_enter.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2009, The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/decelerate_interpolator"
+        android:fromAlpha="0.0" android:toAlpha="1.0" android:duration="400" />
diff --git a/res/anim/on_screen_hint_exit.xml b/res/anim/on_screen_hint_exit.xml
new file mode 100644
index 0000000..2525816
--- /dev/null
+++ b/res/anim/on_screen_hint_exit.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2009, The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@android:anim/accelerate_interpolator"
+        android:fromAlpha="1.0" android:toAlpha="0.0" android:duration="400" />
diff --git a/res/anim/photoeditor_fade_in.xml b/res/anim/photoeditor_fade_in.xml
deleted file mode 100644
index 7334041..0000000
--- a/res/anim/photoeditor_fade_in.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<alpha xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="0.25"
-    android:toAlpha="1.0"
-    android:duration="500"
-    android:fillAfter="true"/>
diff --git a/res/anim/photoeditor_fade_out.xml b/res/anim/photoeditor_fade_out.xml
deleted file mode 100644
index ad97c49..0000000
--- a/res/anim/photoeditor_fade_out.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<alpha xmlns:android="http://schemas.android.com/apk/res/android"
-    android:fromAlpha="1.0"
-    android:toAlpha="0.25"
-    android:duration="500"
-    android:fillAfter="true"/>
diff --git a/res/anim/slide_in_left.xml b/res/anim/slide_in_left.xml
new file mode 100644
index 0000000..6b1de4b
--- /dev/null
+++ b/res/anim/slide_in_left.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromXDelta="-100%"
+           android:toXDelta="0%"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="300"/>
\ No newline at end of file
diff --git a/res/anim/slide_in_right.xml b/res/anim/slide_in_right.xml
new file mode 100644
index 0000000..12f7efe
--- /dev/null
+++ b/res/anim/slide_in_right.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromXDelta="100%"
+           android:toXDelta="0"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="300"/>
\ No newline at end of file
diff --git a/res/anim/slide_out_left.xml b/res/anim/slide_out_left.xml
new file mode 100644
index 0000000..be28e55
--- /dev/null
+++ b/res/anim/slide_out_left.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromXDelta="0%"
+           android:toXDelta="100%"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="300"/>
\ No newline at end of file
diff --git a/res/anim/slide_out_right.xml b/res/anim/slide_out_right.xml
new file mode 100644
index 0000000..4c786e6
--- /dev/null
+++ b/res/anim/slide_out_right.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<translate xmlns:android="http://schemas.android.com/apk/res/android"
+           android:fromXDelta="0"
+           android:toXDelta="-100%"
+           android:interpolator="@android:anim/decelerate_interpolator"
+           android:duration="300"/>
\ No newline at end of file
diff --git a/res/color/primary_text.xml b/res/color/primary_text.xml
new file mode 100644
index 0000000..e9fdf7b
--- /dev/null
+++ b/res/color/primary_text.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- Copied from framework resource color/primary_text_holo_dark.xml -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:color="@color/bright_foreground_disabled_holo_dark"/>
+    <item android:state_window_focused="false" android:color="@color/bright_foreground_holo_dark"/>
+    <item android:state_pressed="true" android:color="@color/bright_foreground_holo_dark"/>
+    <item android:state_selected="true" android:color="@color/bright_foreground_holo_dark"/>
+    <item android:state_activated="true" android:color="@color/bright_foreground_holo_dark"/>
+    <item android:color="@color/bright_foreground_holo_dark"/> <!-- not selected -->
+</selector>
+
diff --git a/res/drawable-hdpi/background_portrait.jpg b/res/drawable-hdpi/background_portrait.jpg
deleted file mode 100644
index 75309b4..0000000
--- a/res/drawable-hdpi/background_portrait.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/btn_default_normal_holo_dark.9.png b/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
deleted file mode 100644
index d608e44..0000000
--- a/res/drawable-hdpi/btn_default_normal_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png b/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
deleted file mode 100644
index 40957ee..0000000
--- a/res/drawable-hdpi/btn_default_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_default.png b/res/drawable-hdpi/btn_shutter_default.png
new file mode 100644
index 0000000..1d59be1
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_pressed.png b/res/drawable-hdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..c732920
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_recording.png b/res/drawable-hdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..4a2e452
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_video_default.png b/res/drawable-hdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..1fe24f2
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_video_pressed.png b/res/drawable-hdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..78f4b16
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_shutter_video_recording.png b/res/drawable-hdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..e9921d0
--- /dev/null
+++ b/res/drawable-hdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_video_shutter_recording_holo.png b/res/drawable-hdpi/btn_video_shutter_recording_holo.png
new file mode 100644
index 0000000..9bd4e2a
--- /dev/null
+++ b/res/drawable-hdpi/btn_video_shutter_recording_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_video_shutter_recording_holo_large.png b/res/drawable-hdpi/btn_video_shutter_recording_holo_large.png
new file mode 100644
index 0000000..c9b2da3
--- /dev/null
+++ b/res/drawable-hdpi/btn_video_shutter_recording_holo_large.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo.png b/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo.png
new file mode 100644
index 0000000..a10b620
--- /dev/null
+++ b/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo_large.png b/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo_large.png
new file mode 100644
index 0000000..864aa1f
--- /dev/null
+++ b/res/drawable-hdpi/btn_video_shutter_recording_pressed_holo_large.png
Binary files differ
diff --git a/res/drawable-hdpi/camera_crop_holo.png b/res/drawable-hdpi/camera_crop_holo.png
deleted file mode 100644
index 0c3506c..0000000
--- a/res/drawable-hdpi/camera_crop_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/capture_thumbnail_shadow.9.png b/res/drawable-hdpi/capture_thumbnail_shadow.9.png
new file mode 100644
index 0000000..f7a664e
--- /dev/null
+++ b/res/drawable-hdpi/capture_thumbnail_shadow.9.png
Binary files differ
diff --git a/res/drawable-hdpi/dialog_full_holo_dark.9.png b/res/drawable-hdpi/dialog_full_holo_dark.9.png
new file mode 100644
index 0000000..79e56f5
--- /dev/null
+++ b/res/drawable-hdpi/dialog_full_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_button_colors_curve.png b/res/drawable-hdpi/filtershow_button_colors_curve.png
index 0b7a260..cc33bf8 100644
--- a/res/drawable-hdpi/filtershow_button_colors_curve.png
+++ b/res/drawable-hdpi/filtershow_button_colors_curve.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_button_colors_sharpen.png b/res/drawable-hdpi/filtershow_button_colors_sharpen.png
new file mode 100644
index 0000000..2835b95
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_button_colors_sharpen.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_button_redo.png b/res/drawable-hdpi/filtershow_button_redo.png
new file mode 100644
index 0000000..43e673c
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_button_redo.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_button_undo.png b/res/drawable-hdpi/filtershow_button_undo.png
new file mode 100644
index 0000000..6165a98
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_button_undo.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_control_disabled.png b/res/drawable-hdpi/filtershow_scrubber_control_disabled.png
new file mode 100644
index 0000000..7ccb7ec
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_control_disabled.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_control_focused.png b/res/drawable-hdpi/filtershow_scrubber_control_focused.png
new file mode 100644
index 0000000..f8c50f9
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_control_focused.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_control_normal.png b/res/drawable-hdpi/filtershow_scrubber_control_normal.png
new file mode 100644
index 0000000..e12ce2e
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_control_normal.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_control_pressed.png b/res/drawable-hdpi/filtershow_scrubber_control_pressed.png
new file mode 100644
index 0000000..8f0ae42
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_control_pressed.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_primary.9.png b/res/drawable-hdpi/filtershow_scrubber_primary.9.png
new file mode 100644
index 0000000..6e007d3
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_primary.9.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_secondary.9.png b/res/drawable-hdpi/filtershow_scrubber_secondary.9.png
new file mode 100644
index 0000000..1054715
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_secondary.9.png
Binary files differ
diff --git a/res/drawable-hdpi/filtershow_scrubber_track.9.png b/res/drawable-hdpi/filtershow_scrubber_track.9.png
new file mode 100644
index 0000000..0c0ccda
--- /dev/null
+++ b/res/drawable-hdpi/filtershow_scrubber_track.9.png
Binary files differ
diff --git a/res/drawable-hdpi/focus_box.9.png b/res/drawable-hdpi/focus_box.9.png
deleted file mode 100644
index a462953..0000000
--- a/res/drawable-hdpi/focus_box.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/frame_overlay_gallery_ptp.png b/res/drawable-hdpi/frame_overlay_gallery_ptp.png
deleted file mode 100644
index 1166810..0000000
--- a/res/drawable-hdpi/frame_overlay_gallery_ptp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_btn_shutter_retake.png b/res/drawable-hdpi/ic_btn_shutter_retake.png
new file mode 100644
index 0000000..cc7a44c
--- /dev/null
+++ b/res/drawable-hdpi/ic_btn_shutter_retake.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_effects_holo_light.png b/res/drawable-hdpi/ic_effects_holo_light.png
new file mode 100644
index 0000000..03106eb
--- /dev/null
+++ b/res/drawable-hdpi/ic_effects_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_effects_holo_light_large.png b/res/drawable-hdpi/ic_effects_holo_light_large.png
new file mode 100644
index 0000000..eac6dba
--- /dev/null
+++ b/res/drawable-hdpi/ic_effects_holo_light_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_effects_holo_light_xlarge.png b/res/drawable-hdpi/ic_effects_holo_light_xlarge.png
new file mode 100644
index 0000000..eac6dba
--- /dev/null
+++ b/res/drawable-hdpi/ic_effects_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_0.png b/res/drawable-hdpi/ic_exposure_0.png
new file mode 100644
index 0000000..ba19c89
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_0.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_holo_light.png b/res/drawable-hdpi/ic_exposure_holo_light.png
new file mode 100644
index 0000000..39662a4
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_n1.png b/res/drawable-hdpi/ic_exposure_n1.png
new file mode 100644
index 0000000..e52dd74
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_n1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_n2.png b/res/drawable-hdpi/ic_exposure_n2.png
new file mode 100644
index 0000000..738973d
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_n2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_n3.png b/res/drawable-hdpi/ic_exposure_n3.png
new file mode 100644
index 0000000..2cd63b4
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_n3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_p1.png b/res/drawable-hdpi/ic_exposure_p1.png
new file mode 100644
index 0000000..065e0bc
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_p1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_p2.png b/res/drawable-hdpi/ic_exposure_p2.png
new file mode 100644
index 0000000..92450c4
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_p2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_exposure_p3.png b/res/drawable-hdpi/ic_exposure_p3.png
new file mode 100644
index 0000000..d1be9f6
--- /dev/null
+++ b/res/drawable-hdpi/ic_exposure_p3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_flash_auto_holo_light.png b/res/drawable-hdpi/ic_flash_auto_holo_light.png
new file mode 100644
index 0000000..a34d63a
--- /dev/null
+++ b/res/drawable-hdpi/ic_flash_auto_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_flash_off_holo_light.png b/res/drawable-hdpi/ic_flash_off_holo_light.png
new file mode 100644
index 0000000..3fe45fc
--- /dev/null
+++ b/res/drawable-hdpi/ic_flash_off_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_flash_on_holo_light.png b/res/drawable-hdpi/ic_flash_on_holo_light.png
new file mode 100644
index 0000000..2d67e45
--- /dev/null
+++ b/res/drawable-hdpi/ic_flash_on_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_gallery_play_big.png b/res/drawable-hdpi/ic_gallery_play_big.png
new file mode 100644
index 0000000..44e0c4e
--- /dev/null
+++ b/res/drawable-hdpi/ic_gallery_play_big.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_hdr.png b/res/drawable-hdpi/ic_hdr.png
new file mode 100644
index 0000000..cda9890
--- /dev/null
+++ b/res/drawable-hdpi/ic_hdr.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_hdr_off.png b/res/drawable-hdpi/ic_hdr_off.png
new file mode 100644
index 0000000..28db73c
--- /dev/null
+++ b/res/drawable-hdpi/ic_hdr_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_imagesize.png b/res/drawable-hdpi/ic_imagesize.png
new file mode 100644
index 0000000..126208b
--- /dev/null
+++ b/res/drawable-hdpi/ic_imagesize.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_0.png b/res/drawable-hdpi/ic_indicator_ev_0.png
new file mode 100644
index 0000000..9092025
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_0.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_n1.png b/res/drawable-hdpi/ic_indicator_ev_n1.png
new file mode 100644
index 0000000..56c5e60
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_n1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_n2.png b/res/drawable-hdpi/ic_indicator_ev_n2.png
new file mode 100644
index 0000000..72838de
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_n2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_n3.png b/res/drawable-hdpi/ic_indicator_ev_n3.png
new file mode 100644
index 0000000..1200d63
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_n3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_p1.png b/res/drawable-hdpi/ic_indicator_ev_p1.png
new file mode 100644
index 0000000..5497c86
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_p1.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_p2.png b/res/drawable-hdpi/ic_indicator_ev_p2.png
new file mode 100644
index 0000000..25107fe
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_p2.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_ev_p3.png b/res/drawable-hdpi/ic_indicator_ev_p3.png
new file mode 100644
index 0000000..6d0c1e0
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_ev_p3.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_flash_auto.png b/res/drawable-hdpi/ic_indicator_flash_auto.png
new file mode 100644
index 0000000..edb34a0
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_flash_auto.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_flash_off.png b/res/drawable-hdpi/ic_indicator_flash_off.png
new file mode 100644
index 0000000..880ad07
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_flash_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_flash_on.png b/res/drawable-hdpi/ic_indicator_flash_on.png
new file mode 100644
index 0000000..ec3d1d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_flash_on.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_loc_off.png b/res/drawable-hdpi/ic_indicator_loc_off.png
new file mode 100644
index 0000000..d5937e4
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_loc_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_loc_on.png b/res/drawable-hdpi/ic_indicator_loc_on.png
new file mode 100644
index 0000000..ae272a1
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_loc_on.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_sce_hdr.png b/res/drawable-hdpi/ic_indicator_sce_hdr.png
new file mode 100644
index 0000000..0b8fdfa
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_sce_hdr.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_sce_off.png b/res/drawable-hdpi/ic_indicator_sce_off.png
new file mode 100644
index 0000000..09bd2c8
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_sce_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_sce_on.png b/res/drawable-hdpi/ic_indicator_sce_on.png
new file mode 100644
index 0000000..ef0f56f
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_sce_on.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_timer_off.png b/res/drawable-hdpi/ic_indicator_timer_off.png
new file mode 100644
index 0000000..e377f89
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_timer_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_timer_on.png b/res/drawable-hdpi/ic_indicator_timer_on.png
new file mode 100644
index 0000000..8ceade6
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_timer_on.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_wb_cloudy.png b/res/drawable-hdpi/ic_indicator_wb_cloudy.png
new file mode 100644
index 0000000..62f5c99
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_wb_daylight.png b/res/drawable-hdpi/ic_indicator_wb_daylight.png
new file mode 100644
index 0000000..ffadb0e
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_wb_daylight.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_wb_fluorescent.png b/res/drawable-hdpi/ic_indicator_wb_fluorescent.png
new file mode 100644
index 0000000..3c30bfb
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_wb_off.png b/res/drawable-hdpi/ic_indicator_wb_off.png
new file mode 100644
index 0000000..abe7405
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_wb_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_indicator_wb_tungsten.png b/res/drawable-hdpi/ic_indicator_wb_tungsten.png
new file mode 100644
index 0000000..6de19d5
--- /dev/null
+++ b/res/drawable-hdpi/ic_indicator_wb_tungsten.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_location.png b/res/drawable-hdpi/ic_location.png
new file mode 100644
index 0000000..ff85d79
--- /dev/null
+++ b/res/drawable-hdpi/ic_location.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_location_off.png b/res/drawable-hdpi/ic_location_off.png
new file mode 100644
index 0000000..ae6e2ea
--- /dev/null
+++ b/res/drawable-hdpi/ic_location_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_lockscreen_chevron_up.png b/res/drawable-hdpi/ic_lockscreen_chevron_up.png
deleted file mode 100644
index 4ffa833..0000000
--- a/res/drawable-hdpi/ic_lockscreen_chevron_up.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_manage_pin.png b/res/drawable-hdpi/ic_manage_pin.png
deleted file mode 100644
index 0b68870..0000000
--- a/res/drawable-hdpi/ic_manage_pin.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_cancel_holo_light.png b/res/drawable-hdpi/ic_menu_cancel_holo_light.png
index 9338a51..e0f85c5 100644
--- a/res/drawable-hdpi/ic_menu_cancel_holo_light.png
+++ b/res/drawable-hdpi/ic_menu_cancel_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_done_holo_light.png b/res/drawable-hdpi/ic_menu_done_holo_light.png
new file mode 100644
index 0000000..923589e
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_done_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_edit_holo_dark.png b/res/drawable-hdpi/ic_menu_edit_holo_dark.png
new file mode 100644
index 0000000..54952f5
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_edit_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_save_holo_light.png b/res/drawable-hdpi/ic_menu_save_holo_light.png
deleted file mode 100644
index b13d2db..0000000
--- a/res/drawable-hdpi/ic_menu_save_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_savephoto_disabled.png b/res/drawable-hdpi/ic_menu_savephoto_disabled.png
new file mode 100755
index 0000000..afb34ec
--- /dev/null
+++ b/res/drawable-hdpi/ic_menu_savephoto_disabled.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_border_fast.9.png b/res/drawable-hdpi/ic_pan_border_fast.9.png
new file mode 100644
index 0000000..cbd6172
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_border_fast.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_border_fast_large.9.png b/res/drawable-hdpi/ic_pan_border_fast_large.9.png
new file mode 100644
index 0000000..b0df823
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_border_fast_large.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_border_fast_xlarge.9.png b/res/drawable-hdpi/ic_pan_border_fast_xlarge.9.png
new file mode 100644
index 0000000..b0df823
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_border_fast_xlarge.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator.png b/res/drawable-hdpi/ic_pan_left_indicator.png
new file mode 100644
index 0000000..c9a6907
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator_fast.png b/res/drawable-hdpi/ic_pan_left_indicator_fast.png
new file mode 100644
index 0000000..841e1e5
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator_fast.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator_fast_large.png b/res/drawable-hdpi/ic_pan_left_indicator_fast_large.png
new file mode 100644
index 0000000..244118f
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator_fast_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator_fast_xlarge.png b/res/drawable-hdpi/ic_pan_left_indicator_fast_xlarge.png
new file mode 100644
index 0000000..244118f
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator_large.png b/res/drawable-hdpi/ic_pan_left_indicator_large.png
new file mode 100644
index 0000000..60d1f98
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_left_indicator_xlarge.png b/res/drawable-hdpi/ic_pan_left_indicator_xlarge.png
new file mode 100644
index 0000000..60d1f98
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_left_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_progression.png b/res/drawable-hdpi/ic_pan_progression.png
new file mode 100644
index 0000000..69650f0
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_progression.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_progression_large.png b/res/drawable-hdpi/ic_pan_progression_large.png
new file mode 100644
index 0000000..afe9188
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_progression_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_progression_xlarge.png b/res/drawable-hdpi/ic_pan_progression_xlarge.png
new file mode 100644
index 0000000..afe9188
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_progression_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator.png b/res/drawable-hdpi/ic_pan_right_indicator.png
new file mode 100644
index 0000000..2c4fe80
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator_fast.png b/res/drawable-hdpi/ic_pan_right_indicator_fast.png
new file mode 100644
index 0000000..a6d7eec
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator_fast.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator_fast_large.png b/res/drawable-hdpi/ic_pan_right_indicator_fast_large.png
new file mode 100644
index 0000000..e57f010
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator_fast_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator_fast_xlarge.png b/res/drawable-hdpi/ic_pan_right_indicator_fast_xlarge.png
new file mode 100644
index 0000000..e57f010
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator_large.png b/res/drawable-hdpi/ic_pan_right_indicator_large.png
new file mode 100644
index 0000000..f9b2ca2
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_right_indicator_xlarge.png b/res/drawable-hdpi/ic_pan_right_indicator_xlarge.png
new file mode 100644
index 0000000..f9b2ca2
--- /dev/null
+++ b/res/drawable-hdpi/ic_pan_right_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_pan_thumb.9.png b/res/drawable-hdpi/ic_pan_thumb.9.png
deleted file mode 100644
index eac45fc..0000000
--- a/res/drawable-hdpi/ic_pan_thumb.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_recording_indicator.png b/res/drawable-hdpi/ic_recording_indicator.png
new file mode 100644
index 0000000..509eb8b
--- /dev/null
+++ b/res/drawable-hdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce.png b/res/drawable-hdpi/ic_sce.png
new file mode 100644
index 0000000..dd8bbd7
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce_action.png b/res/drawable-hdpi/ic_sce_action.png
new file mode 100644
index 0000000..f968592
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce_action.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce_night.png b/res/drawable-hdpi/ic_sce_night.png
new file mode 100644
index 0000000..dc42ec9
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce_night.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce_off.png b/res/drawable-hdpi/ic_sce_off.png
new file mode 100644
index 0000000..4c64578
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce_off.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce_party.png b/res/drawable-hdpi/ic_sce_party.png
new file mode 100644
index 0000000..6c664ba
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce_party.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_sce_sunset.png b/res/drawable-hdpi/ic_sce_sunset.png
new file mode 100644
index 0000000..82f74e6
--- /dev/null
+++ b/res/drawable-hdpi/ic_sce_sunset.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_scn_holo_light.png b/res/drawable-hdpi/ic_scn_holo_light.png
new file mode 100644
index 0000000..6b62dce
--- /dev/null
+++ b/res/drawable-hdpi/ic_scn_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_scn_holo_light_large.png b/res/drawable-hdpi/ic_scn_holo_light_large.png
new file mode 100644
index 0000000..e0dd705
--- /dev/null
+++ b/res/drawable-hdpi/ic_scn_holo_light_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_scn_holo_light_xlarge.png b/res/drawable-hdpi/ic_scn_holo_light_xlarge.png
new file mode 100644
index 0000000..e0dd705
--- /dev/null
+++ b/res/drawable-hdpi/ic_scn_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_settings_holo_light.png b/res/drawable-hdpi/ic_settings_holo_light.png
new file mode 100644
index 0000000..5d315a3
--- /dev/null
+++ b/res/drawable-hdpi/ic_settings_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_snapshot_border.9.png b/res/drawable-hdpi/ic_snapshot_border.9.png
new file mode 100644
index 0000000..e6baffe
--- /dev/null
+++ b/res/drawable-hdpi/ic_snapshot_border.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_snapshot_border_large.9.png b/res/drawable-hdpi/ic_snapshot_border_large.9.png
new file mode 100644
index 0000000..291d36b
--- /dev/null
+++ b/res/drawable-hdpi/ic_snapshot_border_large.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_snapshot_border_xlarge.9.png b/res/drawable-hdpi/ic_snapshot_border_xlarge.9.png
new file mode 100644
index 0000000..291d36b
--- /dev/null
+++ b/res/drawable-hdpi/ic_snapshot_border_xlarge.9.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_back.png b/res/drawable-hdpi/ic_switch_back.png
new file mode 100644
index 0000000..02b1c6b
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_back.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_camera.png b/res/drawable-hdpi/ic_switch_camera.png
new file mode 100644
index 0000000..11dd39a
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_camera.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_front.png b/res/drawable-hdpi/ic_switch_front.png
new file mode 100644
index 0000000..09cbc24
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_front.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_pan.png b/res/drawable-hdpi/ic_switch_pan.png
new file mode 100644
index 0000000..c8161be
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_photo_facing_holo_light.png b/res/drawable-hdpi/ic_switch_photo_facing_holo_light.png
new file mode 100644
index 0000000..968063f
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_photo_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_photo_facing_holo_light_large.png b/res/drawable-hdpi/ic_switch_photo_facing_holo_light_large.png
new file mode 100644
index 0000000..a415e8c
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_photo_facing_holo_light_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_photo_facing_holo_light_xlarge.png b/res/drawable-hdpi/ic_switch_photo_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..a415e8c
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_photo_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_photosphere.png b/res/drawable-hdpi/ic_switch_photosphere.png
new file mode 100644
index 0000000..ea28eae
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_photosphere.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_video.png b/res/drawable-hdpi/ic_switch_video.png
new file mode 100644
index 0000000..7432e8e
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_video.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_video_facing_holo_light.png b/res/drawable-hdpi/ic_switch_video_facing_holo_light.png
new file mode 100644
index 0000000..bc7f3ea
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_video_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_video_facing_holo_light_large.png b/res/drawable-hdpi/ic_switch_video_facing_holo_light_large.png
new file mode 100644
index 0000000..743aea3
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_video_facing_holo_light_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switch_video_facing_holo_light_xlarge.png b/res/drawable-hdpi/ic_switch_video_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..743aea3
--- /dev/null
+++ b/res/drawable-hdpi/ic_switch_video_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_switcher_menu_indicator.png b/res/drawable-hdpi/ic_switcher_menu_indicator.png
new file mode 100644
index 0000000..fc7474c
--- /dev/null
+++ b/res/drawable-hdpi/ic_switcher_menu_indicator.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_timelapse_none.png b/res/drawable-hdpi/ic_timelapse_none.png
new file mode 100644
index 0000000..6283f57
--- /dev/null
+++ b/res/drawable-hdpi/ic_timelapse_none.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_timelapse_none_large.png b/res/drawable-hdpi/ic_timelapse_none_large.png
new file mode 100644
index 0000000..33e462f
--- /dev/null
+++ b/res/drawable-hdpi/ic_timelapse_none_large.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_timelapse_none_xlarge.png b/res/drawable-hdpi/ic_timelapse_none_xlarge.png
new file mode 100644
index 0000000..33e462f
--- /dev/null
+++ b/res/drawable-hdpi/ic_timelapse_none_xlarge.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_timer.png b/res/drawable-hdpi/ic_timer.png
new file mode 100644
index 0000000..a3cec8d
--- /dev/null
+++ b/res/drawable-hdpi/ic_timer.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_background_fields_of_wheat_holo.png b/res/drawable-hdpi/ic_video_effects_background_fields_of_wheat_holo.png
new file mode 100644
index 0000000..963c860
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_background_fields_of_wheat_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_background_intergalactic_holo.png b/res/drawable-hdpi/ic_video_effects_background_intergalactic_holo.png
new file mode 100644
index 0000000..dd375e6
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_background_intergalactic_holo.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_background_normal_holo_dark.png b/res/drawable-hdpi/ic_video_effects_background_normal_holo_dark.png
new file mode 100644
index 0000000..f01e41b
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_background_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_big_eyes_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_big_eyes_holo_dark.png
new file mode 100644
index 0000000..493ae40
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_big_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_big_mouth_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_big_mouth_holo_dark.png
new file mode 100644
index 0000000..bc28c2e
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_big_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_big_nose_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_big_nose_holo_dark.png
new file mode 100644
index 0000000..0df146d
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_big_nose_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_small_eyes_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_small_eyes_holo_dark.png
new file mode 100644
index 0000000..c9db4c7
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_small_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_small_mouth_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_small_mouth_holo_dark.png
new file mode 100644
index 0000000..b284745
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_small_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_video_effects_faces_squeeze_holo_dark.png b/res/drawable-hdpi/ic_video_effects_faces_squeeze_holo_dark.png
new file mode 100644
index 0000000..d228423
--- /dev/null
+++ b/res/drawable-hdpi/ic_video_effects_faces_squeeze_holo_dark.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_view_photosphere.png b/res/drawable-hdpi/ic_view_photosphere.png
new file mode 100644
index 0000000..51c267b
--- /dev/null
+++ b/res/drawable-hdpi/ic_view_photosphere.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wb_auto.png b/res/drawable-hdpi/ic_wb_auto.png
new file mode 100644
index 0000000..0e07a04
--- /dev/null
+++ b/res/drawable-hdpi/ic_wb_auto.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wb_cloudy.png b/res/drawable-hdpi/ic_wb_cloudy.png
new file mode 100644
index 0000000..fb42ada
--- /dev/null
+++ b/res/drawable-hdpi/ic_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wb_fluorescent.png b/res/drawable-hdpi/ic_wb_fluorescent.png
new file mode 100644
index 0000000..fbf8c96
--- /dev/null
+++ b/res/drawable-hdpi/ic_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wb_incandescent.png b/res/drawable-hdpi/ic_wb_incandescent.png
new file mode 100644
index 0000000..295a807
--- /dev/null
+++ b/res/drawable-hdpi/ic_wb_incandescent.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_wb_sunlight.png b/res/drawable-hdpi/ic_wb_sunlight.png
new file mode 100644
index 0000000..d256509
--- /dev/null
+++ b/res/drawable-hdpi/ic_wb_sunlight.png
Binary files differ
diff --git a/res/drawable-hdpi/list_divider.9.png b/res/drawable-hdpi/list_divider.9.png
new file mode 100644
index 0000000..986ab0b
--- /dev/null
+++ b/res/drawable-hdpi/list_divider.9.png
Binary files differ
diff --git a/res/drawable-hdpi/list_divider_large.9.png b/res/drawable-hdpi/list_divider_large.9.png
new file mode 100644
index 0000000..986ab0b
--- /dev/null
+++ b/res/drawable-hdpi/list_divider_large.9.png
Binary files differ
diff --git a/res/drawable-hdpi/list_pressed_holo_light.9.png b/res/drawable-hdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..5654cd6
--- /dev/null
+++ b/res/drawable-hdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-hdpi/list_selector_background_selected.9.png b/res/drawable-hdpi/list_selector_background_selected.9.png
new file mode 100644
index 0000000..cbf50b3
--- /dev/null
+++ b/res/drawable-hdpi/list_selector_background_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/on_screen_hint_frame.9.png b/res/drawable-hdpi/on_screen_hint_frame.9.png
new file mode 100644
index 0000000..8b78d67
--- /dev/null
+++ b/res/drawable-hdpi/on_screen_hint_frame.9.png
Binary files differ
diff --git a/res/drawable-hdpi/photoeditor_tab_selected_focused_holo.9.png b/res/drawable-hdpi/photoeditor_tab_selected_focused_holo.9.png
deleted file mode 100644
index 673e3bf..0000000
--- a/res/drawable-hdpi/photoeditor_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/photoeditor_tab_selected_holo.9.png b/res/drawable-hdpi/photoeditor_tab_selected_holo.9.png
deleted file mode 100644
index d57df98..0000000
--- a/res/drawable-hdpi/photoeditor_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/photoeditor_tab_selected_pressed_holo.9.png b/res/drawable-hdpi/photoeditor_tab_selected_pressed_holo.9.png
deleted file mode 100644
index 6278eef..0000000
--- a/res/drawable-hdpi/photoeditor_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/photoeditor_tab_unselected_focused_holo.9.png b/res/drawable-hdpi/photoeditor_tab_unselected_focused_holo.9.png
deleted file mode 100644
index 294991d..0000000
--- a/res/drawable-hdpi/photoeditor_tab_unselected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/photoeditor_tab_unselected_pressed_holo.9.png b/res/drawable-hdpi/photoeditor_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index aadc6f8..0000000
--- a/res/drawable-hdpi/photoeditor_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/popup_full_dark.9.png b/res/drawable-hdpi/popup_full_dark.9.png
deleted file mode 100644
index 2884abe..0000000
--- a/res/drawable-hdpi/popup_full_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/progress_bg_holo_dark.9.png b/res/drawable-hdpi/progress_bg_holo_dark.9.png
deleted file mode 100644
index 5aea3d9..0000000
--- a/res/drawable-hdpi/progress_bg_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/progress_primary_holo_dark.9.png b/res/drawable-hdpi/progress_primary_holo_dark.9.png
deleted file mode 100644
index f134a59..0000000
--- a/res/drawable-hdpi/progress_primary_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/progress_secondary_holo_dark.9.png b/res/drawable-hdpi/progress_secondary_holo_dark.9.png
deleted file mode 100644
index 22d608a..0000000
--- a/res/drawable-hdpi/progress_secondary_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/switch_bg_focused_holo_dark.9.png b/res/drawable-hdpi/switch_bg_focused_holo_dark.9.png
new file mode 100644
index 0000000..4e2ae0f
--- /dev/null
+++ b/res/drawable-hdpi/switch_bg_focused_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/switch_bg_holo_dark.9.png b/res/drawable-hdpi/switch_bg_holo_dark.9.png
new file mode 100644
index 0000000..933d99b
--- /dev/null
+++ b/res/drawable-hdpi/switch_bg_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png b/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png
new file mode 100644
index 0000000..9c5147e
--- /dev/null
+++ b/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png b/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png
new file mode 100644
index 0000000..a257e26
--- /dev/null
+++ b/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/switch_thumb_holo_dark.9.png b/res/drawable-hdpi/switch_thumb_holo_dark.9.png
new file mode 100644
index 0000000..dd999d6
--- /dev/null
+++ b/res/drawable-hdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png b/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png
new file mode 100644
index 0000000..ea54380
--- /dev/null
+++ b/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-hdpi/toast_frame_holo.9.png b/res/drawable-hdpi/toast_frame_holo.9.png
new file mode 100644
index 0000000..f8f75db
--- /dev/null
+++ b/res/drawable-hdpi/toast_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-land-hdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-land-hdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..9e8ed18
--- /dev/null
+++ b/res/drawable-land-hdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-land-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..c1729ab
--- /dev/null
+++ b/res/drawable-land-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-hdpi/switcher_bg.9.png b/res/drawable-land-hdpi/switcher_bg.9.png
new file mode 100644
index 0000000..dad08d4
--- /dev/null
+++ b/res/drawable-land-hdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-land-mdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-land-mdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..ab1d6c5
--- /dev/null
+++ b/res/drawable-land-mdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-land-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..a0b4fb1
--- /dev/null
+++ b/res/drawable-land-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-mdpi/switcher_bg.9.png b/res/drawable-land-mdpi/switcher_bg.9.png
new file mode 100644
index 0000000..2073686
--- /dev/null
+++ b/res/drawable-land-mdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-land-xhdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..746faf8
--- /dev/null
+++ b/res/drawable-land-xhdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-land-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..26ee6b9
--- /dev/null
+++ b/res/drawable-land-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-land-xhdpi/switcher_bg.9.png b/res/drawable-land-xhdpi/switcher_bg.9.png
new file mode 100644
index 0000000..a726dc8
--- /dev/null
+++ b/res/drawable-land-xhdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-mdpi/background_portrait.jpg b/res/drawable-mdpi/background_portrait.jpg
deleted file mode 100644
index 75309b4..0000000
--- a/res/drawable-mdpi/background_portrait.jpg
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/btn_default_normal_holo_dark.9.png b/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
deleted file mode 100644
index d608e44..0000000
--- a/res/drawable-mdpi/btn_default_normal_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png b/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
deleted file mode 100644
index 40957ee..0000000
--- a/res/drawable-mdpi/btn_default_pressed_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_default.png b/res/drawable-mdpi/btn_shutter_default.png
new file mode 100644
index 0000000..ad21d38
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_pressed.png b/res/drawable-mdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..0895f7a
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_recording.png b/res/drawable-mdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..4906d70
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_video_default.png b/res/drawable-mdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..058ee32
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_video_pressed.png b/res/drawable-mdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..883c36e
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_shutter_video_recording.png b/res/drawable-mdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..a170eba
--- /dev/null
+++ b/res/drawable-mdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_video_shutter_recording_holo.png b/res/drawable-mdpi/btn_video_shutter_recording_holo.png
new file mode 100644
index 0000000..ca4625f
--- /dev/null
+++ b/res/drawable-mdpi/btn_video_shutter_recording_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/btn_video_shutter_recording_pressed_holo.png b/res/drawable-mdpi/btn_video_shutter_recording_pressed_holo.png
new file mode 100644
index 0000000..b91ce1a
--- /dev/null
+++ b/res/drawable-mdpi/btn_video_shutter_recording_pressed_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/camera_crop_holo.png b/res/drawable-mdpi/camera_crop_holo.png
deleted file mode 100644
index 7cf744e..0000000
--- a/res/drawable-mdpi/camera_crop_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/capture_thumbnail_shadow.9.png b/res/drawable-mdpi/capture_thumbnail_shadow.9.png
new file mode 100644
index 0000000..db0473d
--- /dev/null
+++ b/res/drawable-mdpi/capture_thumbnail_shadow.9.png
Binary files differ
diff --git a/res/drawable-mdpi/dialog_full_holo_dark.9.png b/res/drawable-mdpi/dialog_full_holo_dark.9.png
new file mode 100644
index 0000000..fb3660e
--- /dev/null
+++ b/res/drawable-mdpi/dialog_full_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_button_colors_curve.png b/res/drawable-mdpi/filtershow_button_colors_curve.png
index e339d44..75e7c11 100644
--- a/res/drawable-mdpi/filtershow_button_colors_curve.png
+++ b/res/drawable-mdpi/filtershow_button_colors_curve.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_button_colors_sharpen.png b/res/drawable-mdpi/filtershow_button_colors_sharpen.png
new file mode 100644
index 0000000..9b7152d
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_button_colors_sharpen.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_button_redo.png b/res/drawable-mdpi/filtershow_button_redo.png
new file mode 100644
index 0000000..6c5595d
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_button_redo.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_button_undo.png b/res/drawable-mdpi/filtershow_button_undo.png
new file mode 100644
index 0000000..97ee13d
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_button_undo.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_control_disabled.png b/res/drawable-mdpi/filtershow_scrubber_control_disabled.png
new file mode 100644
index 0000000..dec1853
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_control_disabled.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_control_focused.png b/res/drawable-mdpi/filtershow_scrubber_control_focused.png
new file mode 100644
index 0000000..6f7a13d
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_control_focused.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_control_normal.png b/res/drawable-mdpi/filtershow_scrubber_control_normal.png
new file mode 100644
index 0000000..ae79ae3
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_control_normal.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_control_pressed.png b/res/drawable-mdpi/filtershow_scrubber_control_pressed.png
new file mode 100644
index 0000000..3060104
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_control_pressed.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_primary.9.png b/res/drawable-mdpi/filtershow_scrubber_primary.9.png
new file mode 100644
index 0000000..f542580
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_primary.9.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_secondary.9.png b/res/drawable-mdpi/filtershow_scrubber_secondary.9.png
new file mode 100644
index 0000000..1cad029
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_secondary.9.png
Binary files differ
diff --git a/res/drawable-mdpi/filtershow_scrubber_track.9.png b/res/drawable-mdpi/filtershow_scrubber_track.9.png
new file mode 100644
index 0000000..b91a4ee
--- /dev/null
+++ b/res/drawable-mdpi/filtershow_scrubber_track.9.png
Binary files differ
diff --git a/res/drawable-mdpi/focus_box.9.png b/res/drawable-mdpi/focus_box.9.png
deleted file mode 100644
index dd1af94..0000000
--- a/res/drawable-mdpi/focus_box.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/frame_overlay_gallery_ptp.png b/res/drawable-mdpi/frame_overlay_gallery_ptp.png
deleted file mode 100644
index d1f6d69..0000000
--- a/res/drawable-mdpi/frame_overlay_gallery_ptp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_btn_shutter_retake.png b/res/drawable-mdpi/ic_btn_shutter_retake.png
new file mode 100644
index 0000000..dc631db
--- /dev/null
+++ b/res/drawable-mdpi/ic_btn_shutter_retake.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_effects_holo_light.png b/res/drawable-mdpi/ic_effects_holo_light.png
new file mode 100644
index 0000000..f15daaa
--- /dev/null
+++ b/res/drawable-mdpi/ic_effects_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_effects_holo_light_xlarge.png b/res/drawable-mdpi/ic_effects_holo_light_xlarge.png
new file mode 100644
index 0000000..9935f9c
--- /dev/null
+++ b/res/drawable-mdpi/ic_effects_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_0.png b/res/drawable-mdpi/ic_exposure_0.png
new file mode 100644
index 0000000..a3dcee4
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_0.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_holo_light.png b/res/drawable-mdpi/ic_exposure_holo_light.png
new file mode 100644
index 0000000..ebd2f41
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_n1.png b/res/drawable-mdpi/ic_exposure_n1.png
new file mode 100644
index 0000000..e73f8b3
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_n1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_n2.png b/res/drawable-mdpi/ic_exposure_n2.png
new file mode 100644
index 0000000..662c1f8
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_n2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_n3.png b/res/drawable-mdpi/ic_exposure_n3.png
new file mode 100644
index 0000000..e9c5243
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_n3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_p1.png b/res/drawable-mdpi/ic_exposure_p1.png
new file mode 100644
index 0000000..a6703c2
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_p1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_p2.png b/res/drawable-mdpi/ic_exposure_p2.png
new file mode 100644
index 0000000..9e29e4b
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_p2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_exposure_p3.png b/res/drawable-mdpi/ic_exposure_p3.png
new file mode 100644
index 0000000..89e659a
--- /dev/null
+++ b/res/drawable-mdpi/ic_exposure_p3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_flash_auto_holo_light.png b/res/drawable-mdpi/ic_flash_auto_holo_light.png
new file mode 100644
index 0000000..55794e4
--- /dev/null
+++ b/res/drawable-mdpi/ic_flash_auto_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_flash_off_holo_light.png b/res/drawable-mdpi/ic_flash_off_holo_light.png
new file mode 100644
index 0000000..73b8ca8
--- /dev/null
+++ b/res/drawable-mdpi/ic_flash_off_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_flash_on_holo_light.png b/res/drawable-mdpi/ic_flash_on_holo_light.png
new file mode 100644
index 0000000..9c99d37
--- /dev/null
+++ b/res/drawable-mdpi/ic_flash_on_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_gallery_play_big.png b/res/drawable-mdpi/ic_gallery_play_big.png
new file mode 100644
index 0000000..19c4a79
--- /dev/null
+++ b/res/drawable-mdpi/ic_gallery_play_big.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_hdr.png b/res/drawable-mdpi/ic_hdr.png
new file mode 100644
index 0000000..45dae46
--- /dev/null
+++ b/res/drawable-mdpi/ic_hdr.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_hdr_off.png b/res/drawable-mdpi/ic_hdr_off.png
new file mode 100644
index 0000000..c5003e6
--- /dev/null
+++ b/res/drawable-mdpi/ic_hdr_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_imagesize.png b/res/drawable-mdpi/ic_imagesize.png
new file mode 100644
index 0000000..d3f8b62
--- /dev/null
+++ b/res/drawable-mdpi/ic_imagesize.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_0.png b/res/drawable-mdpi/ic_indicator_ev_0.png
new file mode 100644
index 0000000..6634dd9
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_0.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_n1.png b/res/drawable-mdpi/ic_indicator_ev_n1.png
new file mode 100644
index 0000000..528ecd3
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_n1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_n2.png b/res/drawable-mdpi/ic_indicator_ev_n2.png
new file mode 100644
index 0000000..db4deb1
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_n2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_n3.png b/res/drawable-mdpi/ic_indicator_ev_n3.png
new file mode 100644
index 0000000..6b01a56
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_n3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_p1.png b/res/drawable-mdpi/ic_indicator_ev_p1.png
new file mode 100644
index 0000000..9a1f6f3
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_p1.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_p2.png b/res/drawable-mdpi/ic_indicator_ev_p2.png
new file mode 100644
index 0000000..712ded5
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_p2.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_ev_p3.png b/res/drawable-mdpi/ic_indicator_ev_p3.png
new file mode 100644
index 0000000..d01c2c2
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_ev_p3.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_flash_auto.png b/res/drawable-mdpi/ic_indicator_flash_auto.png
new file mode 100644
index 0000000..c60e423
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_flash_auto.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_flash_off.png b/res/drawable-mdpi/ic_indicator_flash_off.png
new file mode 100644
index 0000000..6c2d75a
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_flash_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_flash_on.png b/res/drawable-mdpi/ic_indicator_flash_on.png
new file mode 100644
index 0000000..8427072
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_flash_on.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_loc_off.png b/res/drawable-mdpi/ic_indicator_loc_off.png
new file mode 100644
index 0000000..87841e3
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_loc_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_loc_on.png b/res/drawable-mdpi/ic_indicator_loc_on.png
new file mode 100644
index 0000000..56a3a3b
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_loc_on.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_sce_hdr.png b/res/drawable-mdpi/ic_indicator_sce_hdr.png
new file mode 100644
index 0000000..7907f64
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_sce_hdr.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_sce_off.png b/res/drawable-mdpi/ic_indicator_sce_off.png
new file mode 100644
index 0000000..4bfd91a
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_sce_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_sce_on.png b/res/drawable-mdpi/ic_indicator_sce_on.png
new file mode 100644
index 0000000..6ea6d77
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_sce_on.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_timer_off.png b/res/drawable-mdpi/ic_indicator_timer_off.png
new file mode 100644
index 0000000..c5f8117
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_timer_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_timer_on.png b/res/drawable-mdpi/ic_indicator_timer_on.png
new file mode 100644
index 0000000..03d6c1b
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_timer_on.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_wb_cloudy.png b/res/drawable-mdpi/ic_indicator_wb_cloudy.png
new file mode 100644
index 0000000..858394f
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_wb_daylight.png b/res/drawable-mdpi/ic_indicator_wb_daylight.png
new file mode 100644
index 0000000..b8520e1
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_wb_daylight.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_wb_fluorescent.png b/res/drawable-mdpi/ic_indicator_wb_fluorescent.png
new file mode 100644
index 0000000..cb34af1
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_wb_off.png b/res/drawable-mdpi/ic_indicator_wb_off.png
new file mode 100644
index 0000000..f20ddf5
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_wb_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_indicator_wb_tungsten.png b/res/drawable-mdpi/ic_indicator_wb_tungsten.png
new file mode 100644
index 0000000..ebfb225
--- /dev/null
+++ b/res/drawable-mdpi/ic_indicator_wb_tungsten.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_location.png b/res/drawable-mdpi/ic_location.png
new file mode 100644
index 0000000..57cecba
--- /dev/null
+++ b/res/drawable-mdpi/ic_location.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_location_off.png b/res/drawable-mdpi/ic_location_off.png
new file mode 100644
index 0000000..548fd96
--- /dev/null
+++ b/res/drawable-mdpi/ic_location_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_lockscreen_chevron_up.png b/res/drawable-mdpi/ic_lockscreen_chevron_up.png
deleted file mode 100644
index 35aca4e..0000000
--- a/res/drawable-mdpi/ic_lockscreen_chevron_up.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_manage_pin.png b/res/drawable-mdpi/ic_manage_pin.png
deleted file mode 100644
index 1324585..0000000
--- a/res/drawable-mdpi/ic_manage_pin.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_cancel_holo_light.png b/res/drawable-mdpi/ic_menu_cancel_holo_light.png
index 83776ba..d5ca918 100644
--- a/res/drawable-mdpi/ic_menu_cancel_holo_light.png
+++ b/res/drawable-mdpi/ic_menu_cancel_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_done_holo_light.png b/res/drawable-mdpi/ic_menu_done_holo_light.png
new file mode 100644
index 0000000..d831414
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_done_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_edit_holo_dark.png b/res/drawable-mdpi/ic_menu_edit_holo_dark.png
new file mode 100644
index 0000000..ca9188e
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_edit_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_save_holo_light.png b/res/drawable-mdpi/ic_menu_save_holo_light.png
deleted file mode 100644
index b2a33a2..0000000
--- a/res/drawable-mdpi/ic_menu_save_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_savephoto_disabled.png b/res/drawable-mdpi/ic_menu_savephoto_disabled.png
new file mode 100755
index 0000000..dc9b406
--- /dev/null
+++ b/res/drawable-mdpi/ic_menu_savephoto_disabled.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_border_fast.9.png b/res/drawable-mdpi/ic_pan_border_fast.9.png
new file mode 100644
index 0000000..1435c31
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_border_fast.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_border_fast_xlarge.9.png b/res/drawable-mdpi/ic_pan_border_fast_xlarge.9.png
new file mode 100644
index 0000000..332441e
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_border_fast_xlarge.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_left_indicator.png b/res/drawable-mdpi/ic_pan_left_indicator.png
new file mode 100644
index 0000000..7b52deb
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_left_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_left_indicator_fast.png b/res/drawable-mdpi/ic_pan_left_indicator_fast.png
new file mode 100644
index 0000000..fbabbd2
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_left_indicator_fast.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_left_indicator_fast_xlarge.png b/res/drawable-mdpi/ic_pan_left_indicator_fast_xlarge.png
new file mode 100644
index 0000000..68e17c4
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_left_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_left_indicator_xlarge.png b/res/drawable-mdpi/ic_pan_left_indicator_xlarge.png
new file mode 100644
index 0000000..4579cf7
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_left_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_progression.png b/res/drawable-mdpi/ic_pan_progression.png
new file mode 100644
index 0000000..9425f32
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_progression.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_progression_xlarge.png b/res/drawable-mdpi/ic_pan_progression_xlarge.png
new file mode 100644
index 0000000..d75ec8b
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_progression_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_right_indicator.png b/res/drawable-mdpi/ic_pan_right_indicator.png
new file mode 100644
index 0000000..0e8059f
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_right_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_right_indicator_fast.png b/res/drawable-mdpi/ic_pan_right_indicator_fast.png
new file mode 100644
index 0000000..1917f04
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_right_indicator_fast.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_right_indicator_fast_xlarge.png b/res/drawable-mdpi/ic_pan_right_indicator_fast_xlarge.png
new file mode 100644
index 0000000..dd9794d
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_right_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_right_indicator_xlarge.png b/res/drawable-mdpi/ic_pan_right_indicator_xlarge.png
new file mode 100644
index 0000000..de44206
--- /dev/null
+++ b/res/drawable-mdpi/ic_pan_right_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_pan_thumb.9.png b/res/drawable-mdpi/ic_pan_thumb.9.png
deleted file mode 100644
index 9357de6..0000000
--- a/res/drawable-mdpi/ic_pan_thumb.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_recording_indicator.png b/res/drawable-mdpi/ic_recording_indicator.png
new file mode 100755
index 0000000..aa8781d
--- /dev/null
+++ b/res/drawable-mdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce.png b/res/drawable-mdpi/ic_sce.png
new file mode 100644
index 0000000..724d278
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce_action.png b/res/drawable-mdpi/ic_sce_action.png
new file mode 100644
index 0000000..2cd9a8c
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce_action.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce_night.png b/res/drawable-mdpi/ic_sce_night.png
new file mode 100644
index 0000000..9479262
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce_night.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce_off.png b/res/drawable-mdpi/ic_sce_off.png
new file mode 100644
index 0000000..2a3f950
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce_off.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce_party.png b/res/drawable-mdpi/ic_sce_party.png
new file mode 100644
index 0000000..5f0f9cf
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce_party.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_sce_sunset.png b/res/drawable-mdpi/ic_sce_sunset.png
new file mode 100644
index 0000000..0cede83
--- /dev/null
+++ b/res/drawable-mdpi/ic_sce_sunset.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_scn_holo_light.png b/res/drawable-mdpi/ic_scn_holo_light.png
new file mode 100644
index 0000000..b413d60
--- /dev/null
+++ b/res/drawable-mdpi/ic_scn_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_scn_holo_light_xlarge.png b/res/drawable-mdpi/ic_scn_holo_light_xlarge.png
new file mode 100644
index 0000000..0b3866e
--- /dev/null
+++ b/res/drawable-mdpi/ic_scn_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_settings_holo_light.png b/res/drawable-mdpi/ic_settings_holo_light.png
new file mode 100644
index 0000000..5b39398
--- /dev/null
+++ b/res/drawable-mdpi/ic_settings_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_snapshot_border.9.png b/res/drawable-mdpi/ic_snapshot_border.9.png
new file mode 100644
index 0000000..1fa9978
--- /dev/null
+++ b/res/drawable-mdpi/ic_snapshot_border.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_snapshot_border_xlarge.9.png b/res/drawable-mdpi/ic_snapshot_border_xlarge.9.png
new file mode 100644
index 0000000..6b76066
--- /dev/null
+++ b/res/drawable-mdpi/ic_snapshot_border_xlarge.9.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_back.png b/res/drawable-mdpi/ic_switch_back.png
new file mode 100644
index 0000000..6dce903
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_back.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_camera.png b/res/drawable-mdpi/ic_switch_camera.png
new file mode 100644
index 0000000..a978117
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_camera.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_front.png b/res/drawable-mdpi/ic_switch_front.png
new file mode 100644
index 0000000..c8ff3be
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_front.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_pan.png b/res/drawable-mdpi/ic_switch_pan.png
new file mode 100644
index 0000000..e63b8e9
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_photo_facing_holo_light.png b/res/drawable-mdpi/ic_switch_photo_facing_holo_light.png
new file mode 100644
index 0000000..1e6b72b
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_photo_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_photo_facing_holo_light_xlarge.png b/res/drawable-mdpi/ic_switch_photo_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..cbb0c83
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_photo_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_photosphere.png b/res/drawable-mdpi/ic_switch_photosphere.png
new file mode 100644
index 0000000..1b8db05
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_photosphere.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_video.png b/res/drawable-mdpi/ic_switch_video.png
new file mode 100644
index 0000000..61c37ac
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_video.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_video_facing_holo_light.png b/res/drawable-mdpi/ic_switch_video_facing_holo_light.png
new file mode 100644
index 0000000..205db0b
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_video_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switch_video_facing_holo_light_xlarge.png b/res/drawable-mdpi/ic_switch_video_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..bf353d2
--- /dev/null
+++ b/res/drawable-mdpi/ic_switch_video_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_switcher_menu_indicator.png b/res/drawable-mdpi/ic_switcher_menu_indicator.png
new file mode 100644
index 0000000..d6c72bc
--- /dev/null
+++ b/res/drawable-mdpi/ic_switcher_menu_indicator.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_timelapse_none.png b/res/drawable-mdpi/ic_timelapse_none.png
new file mode 100644
index 0000000..122e6fa
--- /dev/null
+++ b/res/drawable-mdpi/ic_timelapse_none.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_timelapse_none_xlarge.png b/res/drawable-mdpi/ic_timelapse_none_xlarge.png
new file mode 100644
index 0000000..67e36a6
--- /dev/null
+++ b/res/drawable-mdpi/ic_timelapse_none_xlarge.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_timer.png b/res/drawable-mdpi/ic_timer.png
new file mode 100644
index 0000000..b55555f
--- /dev/null
+++ b/res/drawable-mdpi/ic_timer.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_background_fields_of_wheat_holo.png b/res/drawable-mdpi/ic_video_effects_background_fields_of_wheat_holo.png
new file mode 100644
index 0000000..7b4e579
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_background_fields_of_wheat_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_background_intergalactic_holo.png b/res/drawable-mdpi/ic_video_effects_background_intergalactic_holo.png
new file mode 100644
index 0000000..ec9c1bc
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_background_intergalactic_holo.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_background_normal_holo_dark.png b/res/drawable-mdpi/ic_video_effects_background_normal_holo_dark.png
new file mode 100644
index 0000000..f055c48
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_background_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_big_eyes_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_big_eyes_holo_dark.png
new file mode 100644
index 0000000..4759c4f
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_big_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_big_mouth_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_big_mouth_holo_dark.png
new file mode 100644
index 0000000..ebd4eeb
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_big_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_big_nose_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_big_nose_holo_dark.png
new file mode 100644
index 0000000..d292b41
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_big_nose_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_small_eyes_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_small_eyes_holo_dark.png
new file mode 100644
index 0000000..42f55ff
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_small_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_small_mouth_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_small_mouth_holo_dark.png
new file mode 100644
index 0000000..0bab7fb
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_small_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_effects_faces_squeeze_holo_dark.png b/res/drawable-mdpi/ic_video_effects_faces_squeeze_holo_dark.png
new file mode 100644
index 0000000..46630fa
--- /dev/null
+++ b/res/drawable-mdpi/ic_video_effects_faces_squeeze_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_view_photosphere.png b/res/drawable-mdpi/ic_view_photosphere.png
new file mode 100644
index 0000000..9b79e61
--- /dev/null
+++ b/res/drawable-mdpi/ic_view_photosphere.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wb_auto.png b/res/drawable-mdpi/ic_wb_auto.png
new file mode 100644
index 0000000..3da3a5c
--- /dev/null
+++ b/res/drawable-mdpi/ic_wb_auto.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wb_cloudy.png b/res/drawable-mdpi/ic_wb_cloudy.png
new file mode 100644
index 0000000..5dc6fbd
--- /dev/null
+++ b/res/drawable-mdpi/ic_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wb_fluorescent.png b/res/drawable-mdpi/ic_wb_fluorescent.png
new file mode 100644
index 0000000..87c001b
--- /dev/null
+++ b/res/drawable-mdpi/ic_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wb_incandescent.png b/res/drawable-mdpi/ic_wb_incandescent.png
new file mode 100644
index 0000000..f6512ef
--- /dev/null
+++ b/res/drawable-mdpi/ic_wb_incandescent.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_wb_sunlight.png b/res/drawable-mdpi/ic_wb_sunlight.png
new file mode 100644
index 0000000..4a07ec4
--- /dev/null
+++ b/res/drawable-mdpi/ic_wb_sunlight.png
Binary files differ
diff --git a/res/drawable-mdpi/list_divider.9.png b/res/drawable-mdpi/list_divider.9.png
new file mode 100644
index 0000000..986ab0b
--- /dev/null
+++ b/res/drawable-mdpi/list_divider.9.png
Binary files differ
diff --git a/res/drawable-mdpi/list_pressed_holo_light.9.png b/res/drawable-mdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..6e77525
--- /dev/null
+++ b/res/drawable-mdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-mdpi/list_selector_background_selected.9.png b/res/drawable-mdpi/list_selector_background_selected.9.png
new file mode 100644
index 0000000..a4ac1e3
--- /dev/null
+++ b/res/drawable-mdpi/list_selector_background_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/on_screen_hint_frame.9.png b/res/drawable-mdpi/on_screen_hint_frame.9.png
new file mode 100644
index 0000000..8b78d67
--- /dev/null
+++ b/res/drawable-mdpi/on_screen_hint_frame.9.png
Binary files differ
diff --git a/res/drawable-mdpi/photoeditor_tab_selected_focused_holo.9.png b/res/drawable-mdpi/photoeditor_tab_selected_focused_holo.9.png
deleted file mode 100644
index c9972e7..0000000
--- a/res/drawable-mdpi/photoeditor_tab_selected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/photoeditor_tab_selected_holo.9.png b/res/drawable-mdpi/photoeditor_tab_selected_holo.9.png
deleted file mode 100644
index 587337c..0000000
--- a/res/drawable-mdpi/photoeditor_tab_selected_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/photoeditor_tab_selected_pressed_holo.9.png b/res/drawable-mdpi/photoeditor_tab_selected_pressed_holo.9.png
deleted file mode 100644
index 155c4fc..0000000
--- a/res/drawable-mdpi/photoeditor_tab_selected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/photoeditor_tab_unselected_focused_holo.9.png b/res/drawable-mdpi/photoeditor_tab_unselected_focused_holo.9.png
deleted file mode 100644
index f0cecd1..0000000
--- a/res/drawable-mdpi/photoeditor_tab_unselected_focused_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/photoeditor_tab_unselected_pressed_holo.9.png b/res/drawable-mdpi/photoeditor_tab_unselected_pressed_holo.9.png
deleted file mode 100644
index b1223fe..0000000
--- a/res/drawable-mdpi/photoeditor_tab_unselected_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/popup_full_dark.9.png b/res/drawable-mdpi/popup_full_dark.9.png
deleted file mode 100644
index 7b9f291..0000000
--- a/res/drawable-mdpi/popup_full_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/progress_bg_holo_dark.9.png b/res/drawable-mdpi/progress_bg_holo_dark.9.png
deleted file mode 100644
index c5418f9..0000000
--- a/res/drawable-mdpi/progress_bg_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/progress_primary_holo_dark.9.png b/res/drawable-mdpi/progress_primary_holo_dark.9.png
deleted file mode 100644
index bac0a23..0000000
--- a/res/drawable-mdpi/progress_primary_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/progress_secondary_holo_dark.9.png b/res/drawable-mdpi/progress_secondary_holo_dark.9.png
deleted file mode 100644
index 8be8656..0000000
--- a/res/drawable-mdpi/progress_secondary_holo_dark.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/switch_bg_focused_holo_dark.9.png b/res/drawable-mdpi/switch_bg_focused_holo_dark.9.png
new file mode 100644
index 0000000..914e433
--- /dev/null
+++ b/res/drawable-mdpi/switch_bg_focused_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/switch_bg_holo_dark.9.png b/res/drawable-mdpi/switch_bg_holo_dark.9.png
new file mode 100644
index 0000000..b5582b5
--- /dev/null
+++ b/res/drawable-mdpi/switch_bg_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png b/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png
new file mode 100644
index 0000000..3d7c236
--- /dev/null
+++ b/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png b/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png
new file mode 100644
index 0000000..82f05d6
--- /dev/null
+++ b/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/switch_thumb_holo_dark.9.png b/res/drawable-mdpi/switch_thumb_holo_dark.9.png
new file mode 100644
index 0000000..9bc7a68
--- /dev/null
+++ b/res/drawable-mdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png b/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png
new file mode 100644
index 0000000..670dc2e
--- /dev/null
+++ b/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-mdpi/toast_frame_holo.9.png b/res/drawable-mdpi/toast_frame_holo.9.png
new file mode 100644
index 0000000..f8f75db
--- /dev/null
+++ b/res/drawable-mdpi/toast_frame_holo.9.png
Binary files differ
diff --git a/res/drawable-nodpi/brush_marker.png b/res/drawable-nodpi/brush_marker.png
new file mode 100644
index 0000000..24eb747
--- /dev/null
+++ b/res/drawable-nodpi/brush_marker.png
Binary files differ
diff --git a/res/drawable-nodpi/brush_spatter.png b/res/drawable-nodpi/brush_spatter.png
new file mode 100644
index 0000000..ae15c22
--- /dev/null
+++ b/res/drawable-nodpi/brush_spatter.png
Binary files differ
diff --git a/res/drawable-nodpi/filtershow_icon_vignette.png b/res/drawable-nodpi/filtershow_icon_vignette.png
new file mode 100644
index 0000000..88d1a96
--- /dev/null
+++ b/res/drawable-nodpi/filtershow_icon_vignette.png
Binary files differ
diff --git a/res/drawable-nodpi/geometry_shadow.9.png b/res/drawable-nodpi/geometry_shadow.9.png
new file mode 100644
index 0000000..2f7abdc
--- /dev/null
+++ b/res/drawable-nodpi/geometry_shadow.9.png
Binary files differ
diff --git a/res/drawable-port-hdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-port-hdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..8420c06
--- /dev/null
+++ b/res/drawable-port-hdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-port-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..e8441f9
--- /dev/null
+++ b/res/drawable-port-hdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-hdpi/switcher_bg.9.png b/res/drawable-port-hdpi/switcher_bg.9.png
new file mode 100644
index 0000000..e6b74a4
--- /dev/null
+++ b/res/drawable-port-hdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-port-mdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-port-mdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..c35e6ff
--- /dev/null
+++ b/res/drawable-port-mdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-port-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..85a6b13
--- /dev/null
+++ b/res/drawable-port-mdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-mdpi/switcher_bg.9.png b/res/drawable-port-mdpi/switcher_bg.9.png
new file mode 100644
index 0000000..6c87aa3
--- /dev/null
+++ b/res/drawable-port-mdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-port-xhdpi/btn_video_shutter_recording_holo_xlarge.png b/res/drawable-port-xhdpi/btn_video_shutter_recording_holo_xlarge.png
new file mode 100644
index 0000000..fc27944
--- /dev/null
+++ b/res/drawable-port-xhdpi/btn_video_shutter_recording_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png b/res/drawable-port-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
new file mode 100644
index 0000000..f58dba8
--- /dev/null
+++ b/res/drawable-port-xhdpi/btn_video_shutter_recording_pressed_holo_xlarge.png
Binary files differ
diff --git a/res/drawable-port-xhdpi/switcher_bg.9.png b/res/drawable-port-xhdpi/switcher_bg.9.png
new file mode 100644
index 0000000..bbe21ba
--- /dev/null
+++ b/res/drawable-port-xhdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_default.png b/res/drawable-sw600dp-hdpi/btn_shutter_default.png
new file mode 100644
index 0000000..bdd641f
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_pressed.png b/res/drawable-sw600dp-hdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..80f7ef5
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_recording.png b/res/drawable-sw600dp-hdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..80f04ff
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_video_default.png b/res/drawable-sw600dp-hdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..44ea9b0
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_video_pressed.png b/res/drawable-sw600dp-hdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..fc8173d
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-hdpi/btn_shutter_video_recording.png b/res/drawable-sw600dp-hdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..65d9879
--- /dev/null
+++ b/res/drawable-sw600dp-hdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp-land-hdpi/switcher_bg.9.png b/res/drawable-sw600dp-land-hdpi/switcher_bg.9.png
new file mode 100644
index 0000000..21375b1
--- /dev/null
+++ b/res/drawable-sw600dp-land-hdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-land-mdpi/switcher_bg.9.png b/res/drawable-sw600dp-land-mdpi/switcher_bg.9.png
new file mode 100644
index 0000000..bfd996a
--- /dev/null
+++ b/res/drawable-sw600dp-land-mdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-land-xhdpi/switcher_bg.9.png b/res/drawable-sw600dp-land-xhdpi/switcher_bg.9.png
new file mode 100644
index 0000000..35a71db
--- /dev/null
+++ b/res/drawable-sw600dp-land-xhdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_default.png b/res/drawable-sw600dp-mdpi/btn_shutter_default.png
new file mode 100644
index 0000000..bf6cf2c
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_pressed.png b/res/drawable-sw600dp-mdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..d8b29e6
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_recording.png b/res/drawable-sw600dp-mdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..5f56b49
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_video_default.png b/res/drawable-sw600dp-mdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..67f5532
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_video_pressed.png b/res/drawable-sw600dp-mdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..3a1e613
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-mdpi/btn_shutter_video_recording.png b/res/drawable-sw600dp-mdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..ecf0276
--- /dev/null
+++ b/res/drawable-sw600dp-mdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp-port-hdpi/switcher_bg.9.png b/res/drawable-sw600dp-port-hdpi/switcher_bg.9.png
new file mode 100644
index 0000000..250276a
--- /dev/null
+++ b/res/drawable-sw600dp-port-hdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-port-mdpi/switcher_bg.9.png b/res/drawable-sw600dp-port-mdpi/switcher_bg.9.png
new file mode 100644
index 0000000..9c4e293
--- /dev/null
+++ b/res/drawable-sw600dp-port-mdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-port-xhdpi/switcher_bg.9.png b/res/drawable-sw600dp-port-xhdpi/switcher_bg.9.png
new file mode 100644
index 0000000..2d0171e
--- /dev/null
+++ b/res/drawable-sw600dp-port-xhdpi/switcher_bg.9.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_default.png b/res/drawable-sw600dp-xhdpi/btn_shutter_default.png
new file mode 100644
index 0000000..b7aa393
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_pressed.png b/res/drawable-sw600dp-xhdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..4a08947
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_recording.png b/res/drawable-sw600dp-xhdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..0a0e108
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_video_default.png b/res/drawable-sw600dp-xhdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..acb8d21
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_video_pressed.png b/res/drawable-sw600dp-xhdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..7621341
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-sw600dp-xhdpi/btn_shutter_video_recording.png b/res/drawable-sw600dp-xhdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..aa3a4bd
--- /dev/null
+++ b/res/drawable-sw600dp-xhdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-sw600dp/photoeditor_scale_seekbar_color.png b/res/drawable-sw600dp/photoeditor_scale_seekbar_color.png
deleted file mode 100644
index addc629..0000000
--- a/res/drawable-sw600dp/photoeditor_scale_seekbar_color.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp/photoeditor_scale_seekbar_generic.png b/res/drawable-sw600dp/photoeditor_scale_seekbar_generic.png
deleted file mode 100644
index 488f927..0000000
--- a/res/drawable-sw600dp/photoeditor_scale_seekbar_generic.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp/photoeditor_scale_seekbar_light.png b/res/drawable-sw600dp/photoeditor_scale_seekbar_light.png
deleted file mode 100644
index b0b9c0d..0000000
--- a/res/drawable-sw600dp/photoeditor_scale_seekbar_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-sw600dp/photoeditor_scale_seekbar_shadow.png b/res/drawable-sw600dp/photoeditor_scale_seekbar_shadow.png
deleted file mode 100644
index 608acbd..0000000
--- a/res/drawable-sw600dp/photoeditor_scale_seekbar_shadow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_default.png b/res/drawable-xhdpi/btn_shutter_default.png
new file mode 100644
index 0000000..5d2b1d2
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_pressed.png b/res/drawable-xhdpi/btn_shutter_pressed.png
new file mode 100644
index 0000000..303e361
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_recording.png b/res/drawable-xhdpi/btn_shutter_recording.png
new file mode 100644
index 0000000..1bd1743
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_recording.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_video_default.png b/res/drawable-xhdpi/btn_shutter_video_default.png
new file mode 100644
index 0000000..10482f5
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_video_default.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_video_pressed.png b/res/drawable-xhdpi/btn_shutter_video_pressed.png
new file mode 100644
index 0000000..35f1ddc
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_video_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_shutter_video_recording.png b/res/drawable-xhdpi/btn_shutter_video_recording.png
new file mode 100644
index 0000000..ba1fb5c
--- /dev/null
+++ b/res/drawable-xhdpi/btn_shutter_video_recording.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_video_shutter_recording_holo.png b/res/drawable-xhdpi/btn_video_shutter_recording_holo.png
new file mode 100644
index 0000000..e164cae
--- /dev/null
+++ b/res/drawable-xhdpi/btn_video_shutter_recording_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/btn_video_shutter_recording_pressed_holo.png b/res/drawable-xhdpi/btn_video_shutter_recording_pressed_holo.png
new file mode 100644
index 0000000..164cd36
--- /dev/null
+++ b/res/drawable-xhdpi/btn_video_shutter_recording_pressed_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/camera_crop_holo.png b/res/drawable-xhdpi/camera_crop_holo.png
deleted file mode 100644
index 2987410..0000000
--- a/res/drawable-xhdpi/camera_crop_holo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/capture_thumbnail_shadow.9.png b/res/drawable-xhdpi/capture_thumbnail_shadow.9.png
new file mode 100644
index 0000000..3d771f7
--- /dev/null
+++ b/res/drawable-xhdpi/capture_thumbnail_shadow.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/dialog_full_holo_dark.9.png b/res/drawable-xhdpi/dialog_full_holo_dark.9.png
new file mode 100644
index 0000000..f4970ad
--- /dev/null
+++ b/res/drawable-xhdpi/dialog_full_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_button_colors_curve.png b/res/drawable-xhdpi/filtershow_button_colors_curve.png
index 8ab69ae..7d80a59 100644
--- a/res/drawable-xhdpi/filtershow_button_colors_curve.png
+++ b/res/drawable-xhdpi/filtershow_button_colors_curve.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_button_colors_sharpen.png b/res/drawable-xhdpi/filtershow_button_colors_sharpen.png
new file mode 100644
index 0000000..951de4e
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_button_colors_sharpen.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_button_redo.png b/res/drawable-xhdpi/filtershow_button_redo.png
new file mode 100644
index 0000000..61eafb9
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_button_redo.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_button_undo.png b/res/drawable-xhdpi/filtershow_button_undo.png
new file mode 100644
index 0000000..48ff5bc
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_button_undo.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_control_disabled.png b/res/drawable-xhdpi/filtershow_scrubber_control_disabled.png
new file mode 100644
index 0000000..f7ba38f
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_control_disabled.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_control_focused.png b/res/drawable-xhdpi/filtershow_scrubber_control_focused.png
new file mode 100644
index 0000000..d40136c
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_control_focused.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_control_normal.png b/res/drawable-xhdpi/filtershow_scrubber_control_normal.png
new file mode 100644
index 0000000..877935d
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_control_normal.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_control_pressed.png b/res/drawable-xhdpi/filtershow_scrubber_control_pressed.png
new file mode 100644
index 0000000..216aaf8
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_control_pressed.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_primary.9.png b/res/drawable-xhdpi/filtershow_scrubber_primary.9.png
new file mode 100644
index 0000000..aae37aa
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_primary.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_secondary.9.png b/res/drawable-xhdpi/filtershow_scrubber_secondary.9.png
new file mode 100644
index 0000000..defea53
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_secondary.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/filtershow_scrubber_track.9.png b/res/drawable-xhdpi/filtershow_scrubber_track.9.png
new file mode 100644
index 0000000..bfb2048
--- /dev/null
+++ b/res/drawable-xhdpi/filtershow_scrubber_track.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/frame_overlay_gallery_ptp.png b/res/drawable-xhdpi/frame_overlay_gallery_ptp.png
deleted file mode 100644
index 3460c78..0000000
--- a/res/drawable-xhdpi/frame_overlay_gallery_ptp.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_btn_shutter_retake.png b/res/drawable-xhdpi/ic_btn_shutter_retake.png
new file mode 100644
index 0000000..ec8c50e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_btn_shutter_retake.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_effects_holo_light.png b/res/drawable-xhdpi/ic_effects_holo_light.png
new file mode 100644
index 0000000..5756fb7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_effects_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_effects_holo_light_xlarge.png b/res/drawable-xhdpi/ic_effects_holo_light_xlarge.png
new file mode 100644
index 0000000..8590a9e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_effects_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_0.png b/res/drawable-xhdpi/ic_exposure_0.png
new file mode 100644
index 0000000..5752ed7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_0.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_holo_light.png b/res/drawable-xhdpi/ic_exposure_holo_light.png
new file mode 100644
index 0000000..06501ad
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_n1.png b/res/drawable-xhdpi/ic_exposure_n1.png
new file mode 100644
index 0000000..d7fe917
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_n1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_n2.png b/res/drawable-xhdpi/ic_exposure_n2.png
new file mode 100644
index 0000000..5c47b96
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_n2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_n3.png b/res/drawable-xhdpi/ic_exposure_n3.png
new file mode 100644
index 0000000..6bf4ccb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_n3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_p1.png b/res/drawable-xhdpi/ic_exposure_p1.png
new file mode 100644
index 0000000..c30265e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_p1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_p2.png b/res/drawable-xhdpi/ic_exposure_p2.png
new file mode 100644
index 0000000..183d25f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_p2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_exposure_p3.png b/res/drawable-xhdpi/ic_exposure_p3.png
new file mode 100644
index 0000000..581a9a7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_exposure_p3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_flash_auto_holo_light.png b/res/drawable-xhdpi/ic_flash_auto_holo_light.png
new file mode 100644
index 0000000..266d1c2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_flash_auto_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_flash_off_holo_light.png b/res/drawable-xhdpi/ic_flash_off_holo_light.png
new file mode 100644
index 0000000..e289ac2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_flash_off_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_flash_on_holo_light.png b/res/drawable-xhdpi/ic_flash_on_holo_light.png
new file mode 100644
index 0000000..e2c63c7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_flash_on_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_gallery_play_big.png b/res/drawable-xhdpi/ic_gallery_play_big.png
new file mode 100644
index 0000000..f677b26
--- /dev/null
+++ b/res/drawable-xhdpi/ic_gallery_play_big.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_hdr.png b/res/drawable-xhdpi/ic_hdr.png
new file mode 100644
index 0000000..12203a2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_hdr.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_hdr_off.png b/res/drawable-xhdpi/ic_hdr_off.png
new file mode 100644
index 0000000..66fe29e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_hdr_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_imagesize.png b/res/drawable-xhdpi/ic_imagesize.png
new file mode 100644
index 0000000..54fd008
--- /dev/null
+++ b/res/drawable-xhdpi/ic_imagesize.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_0.png b/res/drawable-xhdpi/ic_indicator_ev_0.png
new file mode 100644
index 0000000..b86bb30
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_0.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_n1.png b/res/drawable-xhdpi/ic_indicator_ev_n1.png
new file mode 100644
index 0000000..e99cc66
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_n1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_n2.png b/res/drawable-xhdpi/ic_indicator_ev_n2.png
new file mode 100644
index 0000000..aced71d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_n2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_n3.png b/res/drawable-xhdpi/ic_indicator_ev_n3.png
new file mode 100644
index 0000000..41b9f59
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_n3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_p1.png b/res/drawable-xhdpi/ic_indicator_ev_p1.png
new file mode 100644
index 0000000..2850c95
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_p1.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_p2.png b/res/drawable-xhdpi/ic_indicator_ev_p2.png
new file mode 100644
index 0000000..c6355b1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_p2.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_ev_p3.png b/res/drawable-xhdpi/ic_indicator_ev_p3.png
new file mode 100644
index 0000000..99b860d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_ev_p3.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_flash_auto.png b/res/drawable-xhdpi/ic_indicator_flash_auto.png
new file mode 100644
index 0000000..08f507b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_flash_auto.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_flash_off.png b/res/drawable-xhdpi/ic_indicator_flash_off.png
new file mode 100644
index 0000000..a3580f1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_flash_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_flash_on.png b/res/drawable-xhdpi/ic_indicator_flash_on.png
new file mode 100644
index 0000000..7e05e15
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_flash_on.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_loc_off.png b/res/drawable-xhdpi/ic_indicator_loc_off.png
new file mode 100644
index 0000000..966855c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_loc_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_loc_on.png b/res/drawable-xhdpi/ic_indicator_loc_on.png
new file mode 100644
index 0000000..3a4b44e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_loc_on.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_sce_hdr.png b/res/drawable-xhdpi/ic_indicator_sce_hdr.png
new file mode 100644
index 0000000..318c8fa
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_sce_hdr.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_sce_off.png b/res/drawable-xhdpi/ic_indicator_sce_off.png
new file mode 100644
index 0000000..429d6ec
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_sce_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_sce_on.png b/res/drawable-xhdpi/ic_indicator_sce_on.png
new file mode 100644
index 0000000..8b2440c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_sce_on.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_timer_off.png b/res/drawable-xhdpi/ic_indicator_timer_off.png
new file mode 100644
index 0000000..0bbb9ee
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_timer_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_timer_on.png b/res/drawable-xhdpi/ic_indicator_timer_on.png
new file mode 100644
index 0000000..07cef15
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_timer_on.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_wb_cloudy.png b/res/drawable-xhdpi/ic_indicator_wb_cloudy.png
new file mode 100644
index 0000000..862ad5f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_wb_daylight.png b/res/drawable-xhdpi/ic_indicator_wb_daylight.png
new file mode 100644
index 0000000..5c2a4dd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_wb_daylight.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_wb_fluorescent.png b/res/drawable-xhdpi/ic_indicator_wb_fluorescent.png
new file mode 100644
index 0000000..226b36c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_wb_off.png b/res/drawable-xhdpi/ic_indicator_wb_off.png
new file mode 100644
index 0000000..c5e38b1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_wb_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_indicator_wb_tungsten.png b/res/drawable-xhdpi/ic_indicator_wb_tungsten.png
new file mode 100644
index 0000000..a0b4485
--- /dev/null
+++ b/res/drawable-xhdpi/ic_indicator_wb_tungsten.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_location.png b/res/drawable-xhdpi/ic_location.png
new file mode 100644
index 0000000..ab553dc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_location.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_location_off.png b/res/drawable-xhdpi/ic_location_off.png
new file mode 100644
index 0000000..24594d1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_location_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_cancel_holo_light.png b/res/drawable-xhdpi/ic_menu_cancel_holo_light.png
new file mode 100644
index 0000000..a9e1eb0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_cancel_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_done_holo_light.png b/res/drawable-xhdpi/ic_menu_done_holo_light.png
new file mode 100644
index 0000000..7351f21
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_done_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_edit_holo_dark.png b/res/drawable-xhdpi/ic_menu_edit_holo_dark.png
new file mode 100644
index 0000000..65e72c1
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_edit_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_savephoto_disabled.png b/res/drawable-xhdpi/ic_menu_savephoto_disabled.png
new file mode 100755
index 0000000..fab1c55
--- /dev/null
+++ b/res/drawable-xhdpi/ic_menu_savephoto_disabled.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_border_fast.9.png b/res/drawable-xhdpi/ic_pan_border_fast.9.png
new file mode 100644
index 0000000..1006961
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_border_fast.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_border_fast_xlarge.9.png b/res/drawable-xhdpi/ic_pan_border_fast_xlarge.9.png
new file mode 100644
index 0000000..9eeaa47
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_border_fast_xlarge.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_left_indicator.png b/res/drawable-xhdpi/ic_pan_left_indicator.png
new file mode 100644
index 0000000..cab6c90
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_left_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_left_indicator_fast.png b/res/drawable-xhdpi/ic_pan_left_indicator_fast.png
new file mode 100644
index 0000000..d983a49
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_left_indicator_fast.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_left_indicator_fast_xlarge.png b/res/drawable-xhdpi/ic_pan_left_indicator_fast_xlarge.png
new file mode 100644
index 0000000..b87cf6c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_left_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_left_indicator_xlarge.png b/res/drawable-xhdpi/ic_pan_left_indicator_xlarge.png
new file mode 100644
index 0000000..0bc4d06
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_left_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_progression.png b/res/drawable-xhdpi/ic_pan_progression.png
new file mode 100644
index 0000000..756edb7
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_progression.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_progression_xlarge.png b/res/drawable-xhdpi/ic_pan_progression_xlarge.png
new file mode 100644
index 0000000..22d8a4e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_progression_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_right_indicator.png b/res/drawable-xhdpi/ic_pan_right_indicator.png
new file mode 100644
index 0000000..7ffe6ac
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_right_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_right_indicator_fast.png b/res/drawable-xhdpi/ic_pan_right_indicator_fast.png
new file mode 100644
index 0000000..9c7dc3f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_right_indicator_fast.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_right_indicator_fast_xlarge.png b/res/drawable-xhdpi/ic_pan_right_indicator_fast_xlarge.png
new file mode 100644
index 0000000..b8fc167
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_right_indicator_fast_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_right_indicator_xlarge.png b/res/drawable-xhdpi/ic_pan_right_indicator_xlarge.png
new file mode 100644
index 0000000..640e47d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_pan_right_indicator_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_pan_thumb.9.png b/res/drawable-xhdpi/ic_pan_thumb.9.png
deleted file mode 100644
index 122b721..0000000
--- a/res/drawable-xhdpi/ic_pan_thumb.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_recording_indicator.png b/res/drawable-xhdpi/ic_recording_indicator.png
new file mode 100644
index 0000000..fb3fc69
--- /dev/null
+++ b/res/drawable-xhdpi/ic_recording_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce.png b/res/drawable-xhdpi/ic_sce.png
new file mode 100644
index 0000000..44d0d7a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce_action.png b/res/drawable-xhdpi/ic_sce_action.png
new file mode 100644
index 0000000..d79a7e0
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce_action.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce_night.png b/res/drawable-xhdpi/ic_sce_night.png
new file mode 100644
index 0000000..5dd62fc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce_night.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce_off.png b/res/drawable-xhdpi/ic_sce_off.png
new file mode 100644
index 0000000..0279dcc
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce_off.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce_party.png b/res/drawable-xhdpi/ic_sce_party.png
new file mode 100644
index 0000000..638d2d3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce_party.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_sce_sunset.png b/res/drawable-xhdpi/ic_sce_sunset.png
new file mode 100644
index 0000000..721966e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_sce_sunset.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_scn_holo_light.png b/res/drawable-xhdpi/ic_scn_holo_light.png
new file mode 100644
index 0000000..503f11f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_scn_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_scn_holo_light_xlarge.png b/res/drawable-xhdpi/ic_scn_holo_light_xlarge.png
new file mode 100644
index 0000000..4369205
--- /dev/null
+++ b/res/drawable-xhdpi/ic_scn_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_settings_holo_light.png b/res/drawable-xhdpi/ic_settings_holo_light.png
new file mode 100644
index 0000000..0da5b5e
--- /dev/null
+++ b/res/drawable-xhdpi/ic_settings_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_snapshot_border.9.png b/res/drawable-xhdpi/ic_snapshot_border.9.png
new file mode 100644
index 0000000..aae096b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_snapshot_border.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_snapshot_border_xlarge.9.png b/res/drawable-xhdpi/ic_snapshot_border_xlarge.9.png
new file mode 100644
index 0000000..b4e1332
--- /dev/null
+++ b/res/drawable-xhdpi/ic_snapshot_border_xlarge.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_back.png b/res/drawable-xhdpi/ic_switch_back.png
new file mode 100644
index 0000000..26f5e09
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_back.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_camera.png b/res/drawable-xhdpi/ic_switch_camera.png
new file mode 100644
index 0000000..7d24062
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_camera.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_front.png b/res/drawable-xhdpi/ic_switch_front.png
new file mode 100644
index 0000000..8c0383a
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_front.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_pan.png b/res/drawable-xhdpi/ic_switch_pan.png
new file mode 100644
index 0000000..f17ce2f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_pan.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_photo_facing_holo_light.png b/res/drawable-xhdpi/ic_switch_photo_facing_holo_light.png
new file mode 100644
index 0000000..b44ad4c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_photo_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_photo_facing_holo_light_xlarge.png b/res/drawable-xhdpi/ic_switch_photo_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..cf8edf8
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_photo_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_photosphere.png b/res/drawable-xhdpi/ic_switch_photosphere.png
new file mode 100644
index 0000000..8ff60a3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_photosphere.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_video.png b/res/drawable-xhdpi/ic_switch_video.png
new file mode 100644
index 0000000..ed5f31d
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_video.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_video_facing_holo_light.png b/res/drawable-xhdpi/ic_switch_video_facing_holo_light.png
new file mode 100644
index 0000000..8d4d495
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_video_facing_holo_light.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switch_video_facing_holo_light_xlarge.png b/res/drawable-xhdpi/ic_switch_video_facing_holo_light_xlarge.png
new file mode 100644
index 0000000..d48e403
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switch_video_facing_holo_light_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_switcher_menu_indicator.png b/res/drawable-xhdpi/ic_switcher_menu_indicator.png
new file mode 100644
index 0000000..36252ce
--- /dev/null
+++ b/res/drawable-xhdpi/ic_switcher_menu_indicator.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_timelapse_none.png b/res/drawable-xhdpi/ic_timelapse_none.png
new file mode 100644
index 0000000..265f59b
--- /dev/null
+++ b/res/drawable-xhdpi/ic_timelapse_none.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_timelapse_none_xlarge.png b/res/drawable-xhdpi/ic_timelapse_none_xlarge.png
new file mode 100644
index 0000000..ace6b36
--- /dev/null
+++ b/res/drawable-xhdpi/ic_timelapse_none_xlarge.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_timer.png b/res/drawable-xhdpi/ic_timer.png
new file mode 100644
index 0000000..1764fdd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_timer.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_background_fields_of_wheat_holo.png b/res/drawable-xhdpi/ic_video_effects_background_fields_of_wheat_holo.png
new file mode 100644
index 0000000..cfca0b2
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_background_fields_of_wheat_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_background_intergalactic_holo.png b/res/drawable-xhdpi/ic_video_effects_background_intergalactic_holo.png
new file mode 100644
index 0000000..7426510
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_background_intergalactic_holo.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_background_normal_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_background_normal_holo_dark.png
new file mode 100644
index 0000000..93ca230
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_background_normal_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_big_eyes_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_big_eyes_holo_dark.png
new file mode 100644
index 0000000..43a8ffb
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_big_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_big_mouth_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_big_mouth_holo_dark.png
new file mode 100644
index 0000000..05be947
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_big_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_big_nose_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_big_nose_holo_dark.png
new file mode 100644
index 0000000..2eb8a34
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_big_nose_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_small_eyes_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_small_eyes_holo_dark.png
new file mode 100644
index 0000000..21a5939
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_small_eyes_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_small_mouth_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_small_mouth_holo_dark.png
new file mode 100644
index 0000000..434812f
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_small_mouth_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_effects_faces_squeeze_holo_dark.png b/res/drawable-xhdpi/ic_video_effects_faces_squeeze_holo_dark.png
new file mode 100644
index 0000000..0717522
--- /dev/null
+++ b/res/drawable-xhdpi/ic_video_effects_faces_squeeze_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_view_photosphere.png b/res/drawable-xhdpi/ic_view_photosphere.png
new file mode 100644
index 0000000..23a87be
--- /dev/null
+++ b/res/drawable-xhdpi/ic_view_photosphere.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wb_auto.png b/res/drawable-xhdpi/ic_wb_auto.png
new file mode 100644
index 0000000..cbacd04
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wb_auto.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wb_cloudy.png b/res/drawable-xhdpi/ic_wb_cloudy.png
new file mode 100644
index 0000000..cf8ec8c
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wb_cloudy.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wb_fluorescent.png b/res/drawable-xhdpi/ic_wb_fluorescent.png
new file mode 100644
index 0000000..6e03a85
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wb_fluorescent.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wb_incandescent.png b/res/drawable-xhdpi/ic_wb_incandescent.png
new file mode 100644
index 0000000..367ded3
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wb_incandescent.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_wb_sunlight.png b/res/drawable-xhdpi/ic_wb_sunlight.png
new file mode 100644
index 0000000..497c4bd
--- /dev/null
+++ b/res/drawable-xhdpi/ic_wb_sunlight.png
Binary files differ
diff --git a/res/drawable-xhdpi/list_divider.9.png b/res/drawable-xhdpi/list_divider.9.png
new file mode 100644
index 0000000..e62f011
--- /dev/null
+++ b/res/drawable-xhdpi/list_divider.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/list_pressed_holo_light.9.png b/res/drawable-xhdpi/list_pressed_holo_light.9.png
new file mode 100644
index 0000000..e4b3393
--- /dev/null
+++ b/res/drawable-xhdpi/list_pressed_holo_light.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/list_selector_background_selected.9.png b/res/drawable-xhdpi/list_selector_background_selected.9.png
new file mode 100644
index 0000000..78358fe
--- /dev/null
+++ b/res/drawable-xhdpi/list_selector_background_selected.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_bg_focused_holo_dark.9.png b/res/drawable-xhdpi/switch_bg_focused_holo_dark.9.png
new file mode 100644
index 0000000..e85103d
--- /dev/null
+++ b/res/drawable-xhdpi/switch_bg_focused_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_bg_holo_dark.9.png b/res/drawable-xhdpi/switch_bg_holo_dark.9.png
new file mode 100644
index 0000000..732481e
--- /dev/null
+++ b/res/drawable-xhdpi/switch_bg_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png b/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png
new file mode 100644
index 0000000..ca48bd8
--- /dev/null
+++ b/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png b/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png
new file mode 100644
index 0000000..c3d80f0
--- /dev/null
+++ b/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_thumb_holo_dark.9.png b/res/drawable-xhdpi/switch_thumb_holo_dark.9.png
new file mode 100644
index 0000000..df236df
--- /dev/null
+++ b/res/drawable-xhdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png b/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png
new file mode 100644
index 0000000..4acb32b
--- /dev/null
+++ b/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/toast_frame_holo.9.png b/res/drawable-xhdpi/toast_frame_holo.9.png
new file mode 100644
index 0000000..f8f75db
--- /dev/null
+++ b/res/drawable-xhdpi/toast_frame_holo.9.png
Binary files differ
diff --git a/res/drawable/action_bar_two_line_background.xml b/res/drawable/action_bar_two_line_background.xml
new file mode 100644
index 0000000..a5a1855
--- /dev/null
+++ b/res/drawable/action_bar_two_line_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true"
+        android:drawable="@drawable/list_selector_background_selected" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/bg_pressed.xml b/res/drawable/bg_pressed.xml
new file mode 100644
index 0000000..979cc86
--- /dev/null
+++ b/res/drawable/bg_pressed.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo_light" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/bg_pressed_exit_fading.xml b/res/drawable/bg_pressed_exit_fading.xml
new file mode 100644
index 0000000..d317e8b
--- /dev/null
+++ b/res/drawable/bg_pressed_exit_fading.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+    <item android:state_pressed="true" android:drawable="@drawable/list_pressed_holo_light" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/bg_text_on_preview.xml b/res/drawable/bg_text_on_preview.xml
new file mode 100644
index 0000000..cdc2b04
--- /dev/null
+++ b/res/drawable/bg_text_on_preview.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+   <solid android:color="@color/on_viewfinder_label_background_color"/>
+   <corners android:radius="3dp"/>
+</shape>
diff --git a/res/drawable/btn_new_shutter.xml b/res/drawable/btn_new_shutter.xml
new file mode 100644
index 0000000..7a3eb81
--- /dev/null
+++ b/res/drawable/btn_new_shutter.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_shutter_pressed" />
+    <item android:drawable="@drawable/btn_shutter_default" />
+</selector>
+
diff --git a/res/drawable/btn_new_shutter_video.xml b/res/drawable/btn_new_shutter_video.xml
new file mode 100644
index 0000000..e87b456
--- /dev/null
+++ b/res/drawable/btn_new_shutter_video.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_shutter_video_pressed" />
+    <item android:drawable="@drawable/btn_shutter_video_default" />
+</selector>
+
diff --git a/res/drawable/btn_shutter_video_recording.xml b/res/drawable/btn_shutter_video_recording.xml
new file mode 100644
index 0000000..55967a1
--- /dev/null
+++ b/res/drawable/btn_shutter_video_recording.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_video_shutter_recording_pressed_holo" />
+    <item android:drawable="@drawable/btn_video_shutter_recording_holo" />
+</selector>
+
diff --git a/res/drawable/filtershow_addpoint.png b/res/drawable/filtershow_addpoint.png
new file mode 100644
index 0000000..5abfc74
--- /dev/null
+++ b/res/drawable/filtershow_addpoint.png
Binary files differ
diff --git a/res/drawable/filtershow_background.png b/res/drawable/filtershow_background.png
index e7646c3..22e1641 100644
--- a/res/drawable/filtershow_background.png
+++ b/res/drawable/filtershow_background.png
Binary files differ
diff --git a/res/drawable/filtershow_border_film.png b/res/drawable/filtershow_border_film.png
deleted file mode 100755
index 9fbd637..0000000
--- a/res/drawable/filtershow_border_film.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_border.png b/res/drawable/filtershow_button_border.png
deleted file mode 100644
index 69195a9..0000000
--- a/res/drawable/filtershow_button_border.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_contrast.png b/res/drawable/filtershow_button_colors_contrast.png
deleted file mode 100644
index ccb2dc6..0000000
--- a/res/drawable/filtershow_button_colors_contrast.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_curve.png b/res/drawable/filtershow_button_colors_curve.png
deleted file mode 100644
index 7046bd8..0000000
--- a/res/drawable/filtershow_button_colors_curve.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_sharpen.png b/res/drawable/filtershow_button_colors_sharpen.png
deleted file mode 100644
index 2bd0fff..0000000
--- a/res/drawable/filtershow_button_colors_sharpen.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_colors_vignette.png b/res/drawable/filtershow_button_colors_vignette.png
deleted file mode 100644
index ac3d53f..0000000
--- a/res/drawable/filtershow_button_colors_vignette.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_current.png b/res/drawable/filtershow_button_current.png
deleted file mode 100644
index 8c4b379..0000000
--- a/res/drawable/filtershow_button_current.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_fx.png b/res/drawable/filtershow_button_fx.png
deleted file mode 100644
index c887fe4..0000000
--- a/res/drawable/filtershow_button_fx.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_operations.png b/res/drawable/filtershow_button_operations.png
deleted file mode 100644
index 79e9a44..0000000
--- a/res/drawable/filtershow_button_operations.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_origin.png b/res/drawable/filtershow_button_origin.png
deleted file mode 100644
index 0cd0bc2..0000000
--- a/res/drawable/filtershow_button_origin.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_settings.png b/res/drawable/filtershow_button_settings.png
deleted file mode 100644
index df3925a..0000000
--- a/res/drawable/filtershow_button_settings.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_button_show_original.png b/res/drawable/filtershow_button_show_original.png
deleted file mode 100644
index 925954b..0000000
--- a/res/drawable/filtershow_button_show_original.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/filtershow_color_picker_circle.xml b/res/drawable/filtershow_color_picker_circle.xml
new file mode 100644
index 0000000..4444e0f
--- /dev/null
+++ b/res/drawable/filtershow_color_picker_circle.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval" >
+    <size android:width="20dp" android:height="20dp"/>
+    <corners
+        android:radius="10dp" />
+    <solid android:color="@color/red"/>
+</shape>
+
diff --git a/res/drawable/filtershow_color_picker_roundrect.xml b/res/drawable/filtershow_color_picker_roundrect.xml
new file mode 100644
index 0000000..89add5e
--- /dev/null
+++ b/res/drawable/filtershow_color_picker_roundrect.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+
+    <size android:width="20dp" android:height="20dp"/>
+    <corners android:radius="10dp" />
+    <solid android:color="@color/red"/>
+
+</shape>
+
diff --git a/res/drawable/photoeditor_color.png b/res/drawable/filtershow_drawing.png
similarity index 100%
rename from res/drawable/photoeditor_color.png
rename to res/drawable/filtershow_drawing.png
Binary files differ
diff --git a/res/drawable/filtershow_menu_marker.png b/res/drawable/filtershow_menu_marker.png
new file mode 100644
index 0000000..1537a71
--- /dev/null
+++ b/res/drawable/filtershow_menu_marker.png
Binary files differ
diff --git a/res/drawable/filtershow_scrubber.xml b/res/drawable/filtershow_scrubber.xml
new file mode 100644
index 0000000..744d1da
--- /dev/null
+++ b/res/drawable/filtershow_scrubber.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:drawable="@drawable/filtershow_scrubber_control_disabled" />
+    <item android:state_pressed="true" android:drawable="@drawable/filtershow_scrubber_control_pressed" />
+    <item android:state_selected="true" android:drawable="@drawable/filtershow_scrubber_control_focused" />
+    <item android:drawable="@drawable/filtershow_scrubber_control_normal" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/filtershow_slider.xml b/res/drawable/filtershow_slider.xml
new file mode 100644
index 0000000..23457a6
--- /dev/null
+++ b/res/drawable/filtershow_slider.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@android:id/background"
+          android:drawable="@drawable/filtershow_scrubber_track" />
+    <item android:id="@android:id/secondaryProgress">
+        <scale android:scaleWidth="100%"
+               android:drawable="@drawable/filtershow_scrubber_secondary" />
+    </item>
+    <item android:id="@android:id/progress">
+        <scale android:scaleWidth="100%"
+               android:drawable="@drawable/filtershow_scrubber_primary" />
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/res/drawable/filtershow_state_button_background b/res/drawable/filtershow_state_button_background
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/filtershow_state_button_background
diff --git a/res/drawable/filtershow_tiled_background.xml b/res/drawable/filtershow_tiled_background.xml
new file mode 100644
index 0000000..055fbcd
--- /dev/null
+++ b/res/drawable/filtershow_tiled_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<bitmap
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:src="@drawable/filtershow_background"
+        android:tileMode="repeat"
+        android:dither="false" />
\ No newline at end of file
diff --git a/res/drawable/filtershow_vertical_bar.png b/res/drawable/filtershow_vertical_bar.png
new file mode 100644
index 0000000..5ac0a9f
--- /dev/null
+++ b/res/drawable/filtershow_vertical_bar.png
Binary files differ
diff --git a/res/drawable/filtershow_vertical_line.xml b/res/drawable/filtershow_vertical_line.xml
new file mode 100644
index 0000000..611c7e0
--- /dev/null
+++ b/res/drawable/filtershow_vertical_line.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+    <size android:width="1dp" android:height="20dp"/>
+    <corners android:radius="0dp" />
+    <solid android:color="@color/toolbar_separation_line"/>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/ingest_item_list_selector.xml b/res/drawable/ingest_item_list_selector.xml
new file mode 100644
index 0000000..1a4541f
--- /dev/null
+++ b/res/drawable/ingest_item_list_selector.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@color/ingest_highlight_semitransparent"
+        android:state_checked="true" />
+    <item android:drawable="@color/ingest_highlight_semitransparent"
+        android:state_selected="true" />
+    <item android:drawable="@color/ingest_highlight_semitransparent"
+        android:state_pressed="true" />
+</selector>
\ No newline at end of file
diff --git a/res/drawable/menu_save_photo.xml b/res/drawable/menu_save_photo.xml
new file mode 100644
index 0000000..0b92ac9
--- /dev/null
+++ b/res/drawable/menu_save_photo.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true" android:drawable="@drawable/ic_menu_savephoto" />
+    <item android:state_enabled="false" android:drawable="@drawable/ic_menu_savephoto_disabled" />
+</selector>
diff --git a/res/drawable/pano_direction_left_indicator.xml b/res/drawable/pano_direction_left_indicator.xml
new file mode 100644
index 0000000..a0bfb0a
--- /dev/null
+++ b/res/drawable/pano_direction_left_indicator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+              android:drawable="@drawable/ic_pan_left_indicator" />
+    <item android:drawable="@drawable/ic_pan_left_indicator_fast" />
+</selector>
diff --git a/res/drawable/pano_direction_right_indicator.xml b/res/drawable/pano_direction_right_indicator.xml
new file mode 100644
index 0000000..c3ce377
--- /dev/null
+++ b/res/drawable/pano_direction_right_indicator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+              android:drawable="@drawable/ic_pan_right_indicator" />
+    <item android:drawable="@drawable/ic_pan_right_indicator_fast" />
+</selector>
diff --git a/res/drawable/photoeditor_actionbar_translucent.9.png b/res/drawable/photoeditor_actionbar_translucent.9.png
deleted file mode 100644
index 76775a3..0000000
--- a/res/drawable/photoeditor_actionbar_translucent.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_actionbar_translucent_bottom.9.png b/res/drawable/photoeditor_actionbar_translucent_bottom.9.png
deleted file mode 100644
index 97018f8..0000000
--- a/res/drawable/photoeditor_actionbar_translucent_bottom.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_artistic.png b/res/drawable/photoeditor_artistic.png
deleted file mode 100644
index c887fe4..0000000
--- a/res/drawable/photoeditor_artistic.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_autofix.png b/res/drawable/photoeditor_effect_autofix.png
deleted file mode 100644
index 81f5d12..0000000
--- a/res/drawable/photoeditor_effect_autofix.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_crop.png b/res/drawable/photoeditor_effect_crop.png
deleted file mode 100644
index eb7da1b..0000000
--- a/res/drawable/photoeditor_effect_crop.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_crossprocess.png b/res/drawable/photoeditor_effect_crossprocess.png
deleted file mode 100644
index 30bdec2..0000000
--- a/res/drawable/photoeditor_effect_crossprocess.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_documentary.png b/res/drawable/photoeditor_effect_documentary.png
deleted file mode 100644
index 65970a2..0000000
--- a/res/drawable/photoeditor_effect_documentary.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_doodle.png b/res/drawable/photoeditor_effect_doodle.png
deleted file mode 100644
index d23041f..0000000
--- a/res/drawable/photoeditor_effect_doodle.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_duotone.png b/res/drawable/photoeditor_effect_duotone.png
deleted file mode 100644
index e4c0ff1..0000000
--- a/res/drawable/photoeditor_effect_duotone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_facelift.png b/res/drawable/photoeditor_effect_facelift.png
deleted file mode 100644
index 3e86c1f..0000000
--- a/res/drawable/photoeditor_effect_facelift.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_facetan.png b/res/drawable/photoeditor_effect_facetan.png
deleted file mode 100644
index 673a982..0000000
--- a/res/drawable/photoeditor_effect_facetan.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_filllight.png b/res/drawable/photoeditor_effect_filllight.png
deleted file mode 100644
index d4df563..0000000
--- a/res/drawable/photoeditor_effect_filllight.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_fisheye.png b/res/drawable/photoeditor_effect_fisheye.png
deleted file mode 100644
index 229901d..0000000
--- a/res/drawable/photoeditor_effect_fisheye.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_flip.png b/res/drawable/photoeditor_effect_flip.png
deleted file mode 100644
index dd74813..0000000
--- a/res/drawable/photoeditor_effect_flip.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_grain.png b/res/drawable/photoeditor_effect_grain.png
deleted file mode 100644
index 25946e4..0000000
--- a/res/drawable/photoeditor_effect_grain.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_grayscale.png b/res/drawable/photoeditor_effect_grayscale.png
deleted file mode 100644
index 21b65f9..0000000
--- a/res/drawable/photoeditor_effect_grayscale.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_highlight.png b/res/drawable/photoeditor_effect_highlight.png
deleted file mode 100644
index 33d0b94..0000000
--- a/res/drawable/photoeditor_effect_highlight.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_lomoish.png b/res/drawable/photoeditor_effect_lomoish.png
deleted file mode 100644
index 5dfd5d0..0000000
--- a/res/drawable/photoeditor_effect_lomoish.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_negative.png b/res/drawable/photoeditor_effect_negative.png
deleted file mode 100644
index a649b3b..0000000
--- a/res/drawable/photoeditor_effect_negative.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_posterize.png b/res/drawable/photoeditor_effect_posterize.png
deleted file mode 100644
index 284f003..0000000
--- a/res/drawable/photoeditor_effect_posterize.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_rotate.png b/res/drawable/photoeditor_effect_rotate.png
deleted file mode 100644
index fa50ce2..0000000
--- a/res/drawable/photoeditor_effect_rotate.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_saturation.png b/res/drawable/photoeditor_effect_saturation.png
deleted file mode 100644
index deac1ee..0000000
--- a/res/drawable/photoeditor_effect_saturation.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_sepia.png b/res/drawable/photoeditor_effect_sepia.png
deleted file mode 100644
index dc9d80c..0000000
--- a/res/drawable/photoeditor_effect_sepia.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_shadow.png b/res/drawable/photoeditor_effect_shadow.png
deleted file mode 100644
index ff62275..0000000
--- a/res/drawable/photoeditor_effect_shadow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_sharpen.png b/res/drawable/photoeditor_effect_sharpen.png
deleted file mode 100644
index 2bd0fff..0000000
--- a/res/drawable/photoeditor_effect_sharpen.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_straighten.png b/res/drawable/photoeditor_effect_straighten.png
deleted file mode 100644
index 309eb5a..0000000
--- a/res/drawable/photoeditor_effect_straighten.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_temperature.png b/res/drawable/photoeditor_effect_temperature.png
deleted file mode 100644
index e804d82..0000000
--- a/res/drawable/photoeditor_effect_temperature.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_tint.png b/res/drawable/photoeditor_effect_tint.png
deleted file mode 100644
index c5db58e..0000000
--- a/res/drawable/photoeditor_effect_tint.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_effect_vignette.png b/res/drawable/photoeditor_effect_vignette.png
deleted file mode 100644
index 3a336c5..0000000
--- a/res/drawable/photoeditor_effect_vignette.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_exposure.png b/res/drawable/photoeditor_exposure.png
deleted file mode 100644
index b6e3566..0000000
--- a/res/drawable/photoeditor_exposure.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_fix.png b/res/drawable/photoeditor_fix.png
deleted file mode 100644
index 4b8f3b8..0000000
--- a/res/drawable/photoeditor_fix.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_redo.png b/res/drawable/photoeditor_redo.png
deleted file mode 100644
index 9daa01c..0000000
--- a/res/drawable/photoeditor_redo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_scale_seekbar_color.png b/res/drawable/photoeditor_scale_seekbar_color.png
deleted file mode 100644
index 25ec890..0000000
--- a/res/drawable/photoeditor_scale_seekbar_color.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_scale_seekbar_generic.png b/res/drawable/photoeditor_scale_seekbar_generic.png
deleted file mode 100644
index 5b35aea..0000000
--- a/res/drawable/photoeditor_scale_seekbar_generic.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_scale_seekbar_light.png b/res/drawable/photoeditor_scale_seekbar_light.png
deleted file mode 100644
index 5773bd1..0000000
--- a/res/drawable/photoeditor_scale_seekbar_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_scale_seekbar_shadow.png b/res/drawable/photoeditor_scale_seekbar_shadow.png
deleted file mode 100644
index e534bce..0000000
--- a/res/drawable/photoeditor_scale_seekbar_shadow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_seekbar_thumb.png b/res/drawable/photoeditor_seekbar_thumb.png
deleted file mode 100644
index 0d30816..0000000
--- a/res/drawable/photoeditor_seekbar_thumb.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/photoeditor_toggle_button_background.xml b/res/drawable/photoeditor_toggle_button_background.xml
deleted file mode 100644
index e6cd75d..0000000
--- a/res/drawable/photoeditor_toggle_button_background.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Non focused states -->
-    <item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/transparent" />
-    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/photoeditor_tab_selected_holo" />
-
-    <!-- Focused states -->
-    <item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/photoeditor_tab_unselected_focused_holo" />
-    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="false" android:drawable="@drawable/photoeditor_tab_selected_focused_holo" />
-
-    <!-- Pressed (non focused) -->
-    <item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/photoeditor_tab_unselected_pressed_holo" />
-    <item android:state_focused="false" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/photoeditor_tab_selected_pressed_holo" />
-
-    <!-- Pressed (focused) -->
-    <item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/photoeditor_tab_unselected_pressed_holo" />
-    <item android:state_focused="true" android:state_selected="true"  android:state_pressed="true" android:drawable="@drawable/photoeditor_tab_selected_pressed_holo" />
-</selector>
diff --git a/res/drawable/photoeditor_undo.png b/res/drawable/photoeditor_undo.png
deleted file mode 100644
index 0a7e0d1..0000000
--- a/res/drawable/photoeditor_undo.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/setting_picker.xml b/res/drawable/setting_picker.xml
new file mode 100644
index 0000000..c3bff41
--- /dev/null
+++ b/res/drawable/setting_picker.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_checked="true"
+              android:drawable="@drawable/list_pressed_holo_light" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/drawable/switch_inner_holo_dark.xml b/res/drawable/switch_inner_holo_dark.xml
new file mode 100644
index 0000000..c0b00bb
--- /dev/null
+++ b/res/drawable/switch_inner_holo_dark.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false" android:drawable="@drawable/switch_thumb_disabled_holo_dark" />
+    <item android:state_pressed="true"  android:drawable="@drawable/switch_thumb_pressed_holo_dark" />
+    <item android:state_checked="true"  android:drawable="@drawable/switch_thumb_activated_holo_dark" />
+    <item                               android:drawable="@drawable/switch_thumb_holo_dark" />
+</selector>
diff --git a/res/drawable/switch_track_holo_dark.xml b/res/drawable/switch_track_holo_dark.xml
new file mode 100644
index 0000000..3712a61
--- /dev/null
+++ b/res/drawable/switch_track_holo_dark.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true"  android:drawable="@drawable/switch_bg_focused_holo_dark" />
+    <item                               android:drawable="@drawable/switch_bg_holo_dark" />
+</selector>
diff --git a/res/drawable/white_text_bg_gradient.xml b/res/drawable/white_text_bg_gradient.xml
new file mode 100644
index 0000000..c355ce5
--- /dev/null
+++ b/res/drawable/white_text_bg_gradient.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <gradient
+        android:startColor="#DD000000"
+        android:endColor="#00FFFFFF"
+        android:angle="90"
+     />
+</shape>
\ No newline at end of file
diff --git a/res/interpolator/decelerate_cubic.xml b/res/interpolator/decelerate_cubic.xml
new file mode 100644
index 0000000..0bdd01d
--- /dev/null
+++ b/res/interpolator/decelerate_cubic.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012, 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.
+-->
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:factor="1.5" />
diff --git a/res/interpolator/decelerate_quint.xml b/res/interpolator/decelerate_quint.xml
new file mode 100644
index 0000000..1939141
--- /dev/null
+++ b/res/interpolator/decelerate_quint.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012, 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.
+-->
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+        android:factor="2.5" />
diff --git a/res/layout-land/camera_controls.xml b/res/layout-land/camera_controls.xml
new file mode 100644
index 0000000..96f593a
--- /dev/null
+++ b/res/layout-land/camera_controls.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.camera.ui.CameraControls
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+        <View
+            android:id="@+id/blocker"
+            android:layout_height="match_parent"
+            android:layout_width="@dimen/switcher_size"
+            android:layout_gravity="right" />
+
+        <include layout="@layout/menu_indicators"
+            android:layout_width="64dip"
+            android:layout_height="64dip"
+            android:layout_marginTop="-5dip"
+            android:layout_marginRight="6dip"
+            android:layout_gravity="top|right"/>
+
+        <com.android.camera.ui.PieMenuButton
+            android:id="@+id/menu"
+            style="@style/SwitcherButton"
+            android:contentDescription="@string/accessibility_menu_button"
+            android:layout_gravity="right|top"
+            android:layout_marginRight="2dip" />
+
+        <com.android.camera.ui.CameraSwitcher
+            android:id="@+id/camera_switcher"
+            style="@style/SwitcherButton"
+            android:layout_gravity="right|bottom"
+            android:layout_marginRight="2dip"
+            android:contentDescription="@string/accessibility_mode_picker" />
+
+        <com.android.camera.ShutterButton
+            android:id="@+id/shutter_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="right|center_vertical"
+            android:layout_marginRight="@dimen/shutter_offset"
+            android:clickable="true"
+            android:contentDescription="@string/accessibility_shutter_button"
+            android:focusable="true"
+            android:scaleType="center"
+            android:src="@drawable/btn_new_shutter" />
+
+        <View
+            android:id="@+id/preview_thumb"
+            android:visibility="invisible"
+            android:layout_width="@dimen/capture_size"
+            android:layout_height="@dimen/capture_size"
+            android:layout_gravity="top|right" />
+
+</com.android.camera.ui.CameraControls>
diff --git a/res/layout-land/filtershow_activity.xml b/res/layout-land/filtershow_activity.xml
new file mode 100644
index 0000000..4d098e6
--- /dev/null
+++ b/res/layout-land/filtershow_activity.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:id="@+id/mainView"
+             android:background="@drawable/filtershow_tiled_background">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="horizontal"
+            android:animateLayoutChanges="true">
+
+        <LinearLayout
+                android:layout_weight="1"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:orientation="horizontal">
+
+            <FrameLayout
+                    android:id="@+id/editorContainer"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"/>
+
+            <com.android.gallery3d.filtershow.imageshow.ImageShow
+                    android:id="@+id/imageShow"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1" />
+
+        </LinearLayout>
+
+        <LinearLayout
+                android:id="@+id/mainPanel"
+                android:layout_width="350dip"
+                android:layout_height="match_parent"
+                android:orientation="vertical"
+                android:animateLayoutChanges="true" >
+
+            <FrameLayout android:id="@+id/main_panel_container"
+                         android:layout_width="350dip"
+                         android:layout_height="0dip"
+                         android:layout_weight="1" />
+
+            <FrameLayout
+                    android:layout_gravity="bottom"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone">
+
+
+                <ProgressBar
+                        android:id="@+id/loading"
+                        style="@android:style/Widget.Holo.ProgressBar.Large"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center"
+                        android:indeterminate="true"
+                        android:indeterminateOnly="true"
+                        android:background="@color/background_screen"/>
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/res/layout-land/filtershow_category_panel_new.xml b/res/layout-land/filtershow_category_panel_new.xml
new file mode 100644
index 0000000..10a6c97
--- /dev/null
+++ b/res/layout-land/filtershow_category_panel_new.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="horizontal"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <ListView
+            android:id="@+id/listItems"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_margin="8dip"
+            android:divider="@android:color/transparent"
+            android:dividerHeight="8dip" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-land/filtershow_editor_panel.xml b/res/layout-land/filtershow_editor_panel.xml
new file mode 100644
index 0000000..015fa26
--- /dev/null
+++ b/res/layout-land/filtershow_editor_panel.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/top"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical"
+              android:visibility="visible">
+
+    <Button
+            android:id="@+id/toggle_state"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/imageState"
+            android:background="@color/background_main_toolbar"
+            />
+
+    <FrameLayout android:id="@+id/state_panel_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="0dip"
+                 android:visibility="visible"
+                 android:layout_gravity="top"
+                 android:layout_weight="1" />
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_gravity="bottom">
+
+        <LinearLayout
+                android:id="@+id/controlArea"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_alignParentBottom="true"
+                android:visibility="visible">
+
+            <SeekBar
+                    android:id="@+id/primarySeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    style="@style/FilterShowSlider"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="56dip"
+                android:background="@color/background_main_toolbar"
+                android:orientation="horizontal"
+                android:baselineAligned="false"
+                android:visibility="visible">
+
+            <ImageButton
+                    android:id="@+id/cancelFilter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_gravity="left|center_vertical"
+                    android:background="@android:color/transparent"
+                    android:layout_weight=".1"
+                    android:gravity="center"
+                    android:src="@drawable/ic_menu_cancel_holo_light"
+                    android:textSize="18dip"/>
+
+            <ImageView
+                    android:layout_width="2dp"
+                    android:layout_height="fill_parent"
+                    android:src="@drawable/filtershow_vertical_bar"/>
+
+            <LinearLayout
+                    android:id="@+id/panelAccessoryViewList"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal"
+                    android:visibility="visible">
+
+                <com.android.gallery3d.filtershow.editors.SwapButton
+                        android:id="@+id/applyEffect"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_gravity="center"
+                        android:background="@android:color/transparent"
+                        android:gravity="center"
+                        android:text="@string/apply_effect"
+                        android:textSize="18dip"
+                        android:drawableRight="@drawable/filtershow_menu_marker"
+                        android:textAllCaps="true" />
+
+            </LinearLayout>
+
+            <ImageView
+                    android:layout_width="2dp"
+                    android:layout_height="fill_parent"
+                    android:src="@drawable/filtershow_vertical_bar"/>
+
+            <ImageButton
+                    android:id="@+id/applyFilter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_gravity="right|center_vertical"
+                    android:layout_weight=".1"
+                    android:background="@android:color/transparent"
+                    android:gravity="center"
+                    android:src="@drawable/ic_menu_done_holo_light"
+                    android:textSize="18dip"/>
+        </LinearLayout>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout-land/filtershow_main_panel.xml b/res/layout-land/filtershow_main_panel.xml
new file mode 100644
index 0000000..705eb69
--- /dev/null
+++ b/res/layout-land/filtershow_main_panel.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:baselineAligned="false"
+              android:orientation="vertical"
+              android:animateLayoutChanges="true"
+              android:visibility="visible" >
+
+    <FrameLayout android:id="@+id/state_panel_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="0dip"
+                 android:visibility="visible"
+                 android:layout_gravity="top"
+                 android:layout_weight="1" />
+
+    <FrameLayout android:id="@+id/category_panel_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="0dip"
+                 android:layout_weight="1"/>
+
+    <View
+            android:background="@color/toolbar_separation_line"
+            android:layout_height="1dip"
+            android:layout_width="match_parent"/>
+
+    <com.android.gallery3d.filtershow.CenteredLinearLayout
+            xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+            android:layout_width="match_parent"
+            android:layout_height="48dip"
+            android:layout_gravity="center|bottom"
+            custom:max_width="400dip"
+            android:orientation="vertical">
+
+        <LinearLayout android:layout_width="wrap_content"
+                      android:layout_height="match_parent"
+                      android:background="@color/background_main_toolbar">
+
+            <ImageButton
+                    android:id="@+id/fxButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_effects"/>
+
+            <ImageButton
+                    android:id="@+id/borderButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_border"/>
+
+            <ImageButton
+                    android:id="@+id/geometryButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_fix"/>
+
+            <ImageButton
+                    android:id="@+id/colorsButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_color"/>
+
+        </LinearLayout>
+
+    </com.android.gallery3d.filtershow.CenteredLinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout-land/filtershow_state_panel_new.xml b/res/layout-land/filtershow_state_panel_new.xml
new file mode 100644
index 0000000..c83cd88
--- /dev/null
+++ b/res/layout-land/filtershow_state_panel_new.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            android:scrollbars="none">
+
+        <com.android.gallery3d.filtershow.state.StatePanelTrack
+                android:id="@+id/listStates"
+                android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                custom:elemSize="72dip"
+                custom:elemEndSize="32dip"
+                android:layout_margin="8dip"
+                android:animateLayoutChanges="true" />
+
+    </ScrollView>
+
+    <View
+            android:background="@color/state_panel_separation_line"
+            android:layout_height="6dip"
+            android:layout_width="match_parent"
+            android:paddingTop="8dip"/>
+
+</LinearLayout>
diff --git a/res/layout-land/keyguard_widget.xml b/res/layout-land/keyguard_widget.xml
new file mode 100644
index 0000000..f0f4362
--- /dev/null
+++ b/res/layout-land/keyguard_widget.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/default_background" >
+
+    <ImageView
+        android:id="@+id/shutter_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="@dimen/shutter_offset"
+        android:contentDescription="@string/accessibility_shutter_button"
+        android:scaleType="center"
+        android:src="@drawable/btn_new_shutter" />
+
+    <include
+        android:layout_width="64dip"
+        android:layout_height="64dip"
+        android:layout_above="@id/shutter_button"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="6dip"
+        android:layout_marginTop="-5dip"
+        layout="@layout/menu_indicators_keyguard" />
+
+    <ImageView
+        android:id="@+id/camera_switcher"
+        style="@style/SwitcherButton"
+        android:layout_below="@id/shutter_button"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="2dip"
+        android:contentDescription="@string/accessibility_mode_picker"
+        android:scaleType="center"
+        android:src="@drawable/ic_switch_camera" />
+
+    <ImageView
+        android:id="@+id/camera_switcher_ind"
+        style="@style/SwitcherButton"
+        android:layout_below="@id/shutter_button"
+        android:layout_alignParentRight="true"
+        android:layout_marginRight="2dip"
+        android:contentDescription="@string/accessibility_mode_picker"
+        android:scaleType="center"
+        android:src="@drawable/ic_switcher_menu_indicator" />
+
+</RelativeLayout>
diff --git a/res/layout-land/on_screen_hint.xml b/res/layout-land/on_screen_hint.xml
new file mode 100644
index 0000000..59b0d1a
--- /dev/null
+++ b/res/layout-land/on_screen_hint.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012, 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        android:background="@drawable/on_screen_hint_frame">
+    <TextView
+            android:id="@+id/message"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textAppearance="@style/OnScreenHintTextAppearance.Small"
+            android:textColor="#ffffffff"
+            android:shadowColor="#BB000000"
+            android:shadowRadius="2.75" />
+</LinearLayout>
diff --git a/res/layout-land/pano_module_capture.xml b/res/layout-land/pano_module_capture.xml
new file mode 100644
index 0000000..26cbfb1
--- /dev/null
+++ b/res/layout-land/pano_module_capture.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/camera_app"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_gravity="center"
+        android:orientation="horizontal">
+
+    <include layout="@layout/preview_frame_pano" />
+
+</LinearLayout>
diff --git a/res/layout-land/pano_review.xml b/res/layout-land/pano_review.xml
new file mode 100644
index 0000000..ea65c26
--- /dev/null
+++ b/res/layout-land/pano_review.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/pano_review_layout"
+        android:visibility="gone"
+        android:orientation="vertical"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <TextView style="@style/PanoViewHorizontalBar"
+            android:text="@string/pano_review_rendering"
+            android:textAppearance="?android:textAppearanceMedium"
+            android:gravity="center" />
+
+    <ImageView android:id="@+id/pano_reviewarea"
+            android:scaleType="fitCenter"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/pano_mosaic_surface_height" />
+
+    <RelativeLayout style="@style/PanoViewHorizontalBar">
+        <com.android.camera.ui.RotateLayout
+                android:id="@+id/pano_saving_progress_bar_layout"
+                android:layout_centerInParent="true"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <com.android.camera.PanoProgressBar
+                    android:id="@+id/pano_saving_progress_bar"
+                    android:src="@drawable/ic_pan_progression"
+                    android:layout_centerInParent="true"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content" />
+        </com.android.camera.ui.RotateLayout>
+
+        <com.android.camera.ui.RotateImageView android:id="@+id/pano_review_cancel_button"
+                style="@style/ReviewControlIcon"
+                android:contentDescription="@string/accessibility_review_cancel"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:layout_centerHorizontal="false"
+                android:src="@drawable/ic_menu_cancel_holo_light" />
+    </RelativeLayout>
+</LinearLayout>
diff --git a/res/layout-land/preview_frame_pano.xml b/res/layout-land/preview_frame_pano.xml
new file mode 100644
index 0000000..bd05f9a
--- /dev/null
+++ b/res/layout-land/preview_frame_pano.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/frame_layout"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+
+    <LinearLayout android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:orientation="vertical">
+
+        <!-- The top bar with capture indication -->
+        <FrameLayout style="@style/PanoViewHorizontalBar">
+            <TextView android:id="@+id/pano_capture_indicator"
+                    android:text="@string/pano_capture_indication"
+                    android:textAppearance="?android:textAppearanceMedium"
+                    android:layout_gravity="center"
+                    android:visibility="gone"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+        </FrameLayout>
+
+        <com.android.camera.ui.LayoutNotifyView
+                android:id="@+id/pano_preview_area"
+                android:visibility="invisible"
+                android:background="@drawable/ic_pan_border_fast"
+                android:layout_gravity="center"
+                android:layout_weight="5"
+                android:layout_width="match_parent"
+                android:layout_height="0dp" />
+
+        <!-- The bottom bar with progress bar and direction indicators -->
+        <RelativeLayout style="@style/PanoViewHorizontalBar">
+
+            <com.android.camera.ui.RotateLayout
+                    android:id="@+id/pano_pan_progress_bar_layout"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_centerInParent="true">
+                <com.android.camera.PanoProgressBar
+                        android:id="@+id/pano_pan_progress_bar"
+                        android:visibility="gone"
+                        android:src="@drawable/ic_pan_progression"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+            </com.android.camera.ui.RotateLayout>
+            <ImageView
+                    android:id="@+id/pano_pan_left_indicator"
+                    android:src="@drawable/pano_direction_left_indicator"
+                    android:visibility="gone"
+                    android:layout_marginRight="5dp"
+                    android:layout_toLeftOf="@id/pano_pan_progress_bar_layout"
+                    android:layout_centerVertical="true"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+
+            <ImageView
+                    android:id="@+id/pano_pan_right_indicator"
+                    android:src="@drawable/pano_direction_right_indicator"
+                    android:visibility="gone"
+                    android:layout_marginLeft="5dp"
+                    android:layout_toRightOf="@id/pano_pan_progress_bar_layout"
+                    android:layout_centerVertical="true"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+        </RelativeLayout>
+
+    </LinearLayout>
+
+    <!-- The hint for "Too fast" text view -->
+    <com.android.camera.ui.RotateLayout
+            android:id="@+id/pano_capture_too_fast_textview_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true">
+        <TextView android:id="@+id/pano_capture_too_fast_textview"
+                android:text="@string/pano_too_fast_prompt"
+                android:textAppearance="?android:textAppearanceMedium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone" />
+    </com.android.camera.ui.RotateLayout>
+</RelativeLayout>
diff --git a/res/layout-land/review_module_control.xml b/res/layout-land/review_module_control.xml
new file mode 100644
index 0000000..9f8b0cd
--- /dev/null
+++ b/res/layout-land/review_module_control.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<com.android.camera.ui.RotatableLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/CameraControls"
+        android:layout_gravity="right|center_vertical"
+        android:layout_marginRight="2dip">
+    <ImageView android:id="@+id/btn_done"
+            style="@style/ReviewControlIcon"
+            android:contentDescription="@string/accessibility_review_ok"
+            android:visibility="gone"
+            android:scaleType="center"
+            android:layout_gravity="top|right"
+            android:background="@drawable/bg_pressed"
+            android:src="@drawable/ic_menu_done_holo_light" />
+
+    <ImageView android:id="@+id/btn_retake"
+        style="@style/ReviewControlIcon"
+        android:contentDescription="@string/accessibility_review_retake"
+        android:layout_gravity="right|center_vertical"
+        android:scaleType="center"
+        android:focusable="true"
+        android:visibility="gone"
+        android:background="@drawable/bg_pressed"
+        android:src="@drawable/ic_btn_shutter_retake" />
+
+    <ImageView android:id="@+id/btn_cancel"
+            style="@style/ReviewControlIcon"
+            android:contentDescription="@string/accessibility_review_cancel"
+            android:visibility="gone"
+            android:scaleType="center"
+            android:layout_gravity="bottom|right"
+            android:background="@drawable/bg_pressed"
+            android:src="@drawable/ic_menu_cancel_holo_light" />
+</com.android.camera.ui.RotatableLayout>
diff --git a/res/layout-land/switcher_popup.xml b/res/layout-land/switcher_popup.xml
new file mode 100644
index 0000000..fc2d7bc
--- /dev/null
+++ b/res/layout-land/switcher_popup.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:orientation="horizontal"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom|right"
+    android:layout_marginRight="8dip"
+    android:layout_marginBottom="8dip"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    android:paddingTop="16dip"
+    android:paddingBottom="16dip"
+    android:background="#80000000" />
diff --git a/res/layout-port/camera_controls.xml b/res/layout-port/camera_controls.xml
new file mode 100644
index 0000000..ebbdf26
--- /dev/null
+++ b/res/layout-port/camera_controls.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.camera.ui.CameraControls
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+        <View
+            android:id="@+id/blocker"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/switcher_size"
+            android:layout_gravity="bottom" />
+
+        <include layout="@layout/menu_indicators"
+            android:layout_width="64dip"
+            android:layout_height="64dip"
+            android:layout_gravity="bottom|right"
+            android:layout_marginBottom="6dip"
+            android:layout_marginRight="-5dip" />
+
+        <com.android.camera.ui.PieMenuButton
+            android:id="@+id/menu"
+            style="@style/SwitcherButton"
+            android:layout_gravity="bottom|right"
+            android:layout_marginBottom="2dip"
+            android:contentDescription="@string/accessibility_menu_button" />
+
+        <com.android.camera.ui.CameraSwitcher
+           android:id="@+id/camera_switcher"
+           style="@style/SwitcherButton"
+           android:layout_gravity="bottom|left"
+           android:layout_marginBottom="2dip"
+           android:contentDescription="@string/accessibility_mode_picker" />
+
+        <com.android.camera.ShutterButton
+            android:id="@+id/shutter_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/shutter_offset"
+            android:clickable="true"
+            android:contentDescription="@string/accessibility_shutter_button"
+            android:focusable="true"
+            android:scaleType="center"
+            android:src="@drawable/btn_new_shutter" />
+
+        <View
+            android:id="@+id/preview_thumb"
+            android:visibility="invisible"
+            android:layout_width="@dimen/capture_size"
+            android:layout_height="@dimen/capture_size"
+            android:layout_gravity="top|right" />
+
+</com.android.camera.ui.CameraControls>
\ No newline at end of file
diff --git a/res/layout-port/keyguard_widget.xml b/res/layout-port/keyguard_widget.xml
new file mode 100644
index 0000000..28b59c4
--- /dev/null
+++ b/res/layout-port/keyguard_widget.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_controls"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/default_background" >
+
+    <ImageView
+        android:id="@+id/shutter"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_centerHorizontal="true"
+        android:layout_marginBottom="@dimen/shutter_offset"
+        android:src="@drawable/btn_new_shutter" />
+
+    <include layout="@layout/menu_indicators_keyguard"
+        android:layout_width="64dip"
+        android:layout_height="64dip"
+        android:layout_toRightOf="@id/shutter"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="6dip"
+        android:layout_marginRight="-5dip" />
+
+    <ImageView
+        android:id="@+id/camera_switcher"
+        style="@style/SwitcherButton"
+        android:layout_toLeftOf="@id/shutter"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="2dip"
+        android:scaleType="center"
+        android:contentDescription="@string/accessibility_mode_picker"
+        android:src="@drawable/ic_switch_camera" />
+
+    <ImageView
+        android:id="@+id/camera_switcher_ind"
+        style="@style/SwitcherButton"
+        android:layout_toLeftOf="@id/shutter"
+        android:layout_alignParentBottom="true"
+        android:layout_marginBottom="2dip"
+        android:scaleType="center"
+        android:contentDescription="@string/accessibility_mode_picker"
+        android:src="@drawable/ic_switcher_menu_indicator" />
+
+</RelativeLayout>
diff --git a/res/layout-port/on_screen_hint.xml b/res/layout-port/on_screen_hint.xml
new file mode 100644
index 0000000..467b67f
--- /dev/null
+++ b/res/layout-port/on_screen_hint.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2009, The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="horizontal"
+        android:background="@drawable/on_screen_hint_frame">
+    <TextView
+            android:id="@+id/message"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:textAppearance="@style/OnScreenHintTextAppearance.Small"
+            android:textColor="#ffffffff"
+            android:shadowColor="#BB000000"
+            android:shadowRadius="2.75" />
+</LinearLayout>
+
+
diff --git a/res/layout-port/pano_module_capture.xml b/res/layout-port/pano_module_capture.xml
new file mode 100644
index 0000000..d9c9877
--- /dev/null
+++ b/res/layout-port/pano_module_capture.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/camera_app"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:orientation="vertical">
+
+    <include layout="@layout/preview_frame_pano" />
+
+</LinearLayout>
diff --git a/res/layout-port/pano_review.xml b/res/layout-port/pano_review.xml
new file mode 100644
index 0000000..b2e3d8d
--- /dev/null
+++ b/res/layout-port/pano_review.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/pano_review_layout"
+        android:visibility="gone"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent">
+        <TextView style="@style/PanoViewHorizontalBar"
+                android:text="@string/pano_review_rendering"
+                android:textAppearance="?android:textAppearanceMedium"
+                android:gravity="center" />
+
+        <com.android.camera.ui.RotateLayout
+                android:id="@+id/pano_rotate_reviewarea"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1.5">
+            <ImageView android:id="@+id/pano_reviewarea"
+                    android:scaleType="fitCenter"
+                    android:layout_height="match_parent"
+                    android:layout_width="match_parent" />
+        </com.android.camera.ui.RotateLayout>
+
+        <View style="@style/PanoViewHorizontalBar"/>
+    </LinearLayout>
+
+    <com.android.camera.ui.RotateLayout
+            android:id="@+id/pano_saving_progress_bar_layout"
+            android:layout_centerHorizontal="true"
+            android:layout_above="@+id/shutter_button_placeholder"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+        <com.android.camera.PanoProgressBar
+                android:id="@+id/pano_saving_progress_bar"
+                android:src="@drawable/ic_pan_progression"
+                android:layout_centerInParent="true"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content" />
+    </com.android.camera.ui.RotateLayout>
+
+    <ImageView android:id="@id/shutter_button_placeholder"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="@dimen/shutter_offset"
+            android:visibility="invisible"
+            android:layout_gravity="center"
+            android:src="@drawable/btn_shutter_default"/>
+
+    <com.android.camera.ui.RotateImageView android:id="@id/pano_review_cancel_button"
+            style="@style/ReviewControlIcon"
+            android:contentDescription="@string/accessibility_review_cancel"
+            android:layout_alignParentBottom="true"
+            android:layout_centerHorizontal="true"
+            android:layout_centerVertical="false"
+            android:src="@drawable/ic_menu_cancel_holo_light" />
+</RelativeLayout>
diff --git a/res/layout-port/preview_frame_pano.xml b/res/layout-port/preview_frame_pano.xml
new file mode 100644
index 0000000..09d7899
--- /dev/null
+++ b/res/layout-port/preview_frame_pano.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/frame_layout"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+    <LinearLayout android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:baselineAligned="false"
+            android:orientation="vertical">
+        <FrameLayout style="@style/PanoViewHorizontalBar">
+            <TextView android:id="@+id/pano_capture_indicator"
+                    android:text="@string/pano_capture_indication"
+                    android:textAppearance="?android:textAppearanceMedium"
+                    android:visibility="gone"
+                    android:layout_gravity="center"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+        </FrameLayout>
+
+        <com.android.camera.ui.LayoutNotifyView
+                android:id="@+id/pano_preview_area"
+                android:visibility="invisible"
+                android:background="@drawable/ic_pan_border_fast"
+                android:layout_gravity="center"
+                android:layout_weight="2"
+                android:layout_width="match_parent"
+                android:layout_height="0dp" />
+
+        <View style="@style/PanoViewHorizontalBar"/>
+    </LinearLayout>
+
+    <!-- The hint for "Too fast" text view -->
+    <com.android.camera.ui.RotateLayout
+            android:id="@+id/pano_capture_too_fast_textview_layout"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true">
+        <TextView android:id="@+id/pano_capture_too_fast_textview"
+                android:text="@string/pano_too_fast_prompt"
+                android:textAppearance="?android:textAppearanceMedium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:visibility="gone" />
+    </com.android.camera.ui.RotateLayout>
+
+    <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_above="@+id/placeholder">
+        <com.android.camera.ui.RotateLayout
+                android:id="@+id/pano_pan_progress_bar_layout"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerInParent="true">
+            <com.android.camera.PanoProgressBar
+                    android:id="@+id/pano_pan_progress_bar"
+                    android:visibility="gone"
+                    android:src="@drawable/ic_pan_progression"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" />
+        </com.android.camera.ui.RotateLayout>
+
+        <ImageView
+                android:id="@+id/pano_pan_left_indicator"
+                android:src="@drawable/pano_direction_left_indicator"
+                android:visibility="gone"
+                android:layout_marginRight="5dp"
+                android:layout_toLeftOf="@id/pano_pan_progress_bar_layout"
+                android:layout_centerVertical="true"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+
+        <ImageView
+                android:id="@+id/pano_pan_right_indicator"
+                android:src="@drawable/pano_direction_right_indicator"
+                android:visibility="gone"
+                android:layout_marginLeft="5dp"
+                android:layout_toRightOf="@id/pano_pan_progress_bar_layout"
+                android:layout_centerVertical="true"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+    </RelativeLayout>
+
+    <ImageView
+            android:id="@id/placeholder"
+            android:visibility="invisible"
+            android:layout_centerHorizontal="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="@dimen/shutter_offset"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/btn_shutter_default" />
+
+</RelativeLayout>
diff --git a/res/layout-port/review_module_control.xml b/res/layout-port/review_module_control.xml
new file mode 100644
index 0000000..3c4280e
--- /dev/null
+++ b/res/layout-port/review_module_control.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<com.android.camera.ui.RotatableLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/CameraControls"
+        android:layout_gravity="bottom|center_horizontal"
+        android:layout_marginBottom="2dip">
+    <ImageView android:id="@+id/btn_done"
+            style="@style/ReviewControlIcon"
+            android:contentDescription="@string/accessibility_review_ok"
+            android:visibility="gone"
+            android:scaleType="center"
+            android:layout_gravity="right|bottom"
+            android:background="@drawable/bg_pressed"
+            android:src="@drawable/ic_menu_done_holo_light" />
+
+    <ImageView android:id="@+id/btn_retake"
+        style="@style/ReviewControlIcon"
+        android:contentDescription="@string/accessibility_review_retake"
+        android:layout_gravity="bottom|center_horizontal"
+        android:scaleType="center"
+        android:focusable="true"
+        android:visibility="gone"
+        android:background="@drawable/bg_pressed"
+        android:src="@drawable/ic_btn_shutter_retake" />
+
+    <ImageView android:id="@+id/btn_cancel"
+            style="@style/ReviewControlIcon"
+            android:contentDescription="@string/accessibility_review_cancel"
+            android:visibility="gone"
+            android:scaleType="center"
+            android:layout_gravity="left|bottom"
+            android:background="@drawable/bg_pressed"
+            android:src="@drawable/ic_menu_cancel_holo_light" />
+</com.android.camera.ui.RotatableLayout>
diff --git a/res/layout-port/switcher_popup.xml b/res/layout-port/switcher_popup.xml
new file mode 100644
index 0000000..8fe09a3
--- /dev/null
+++ b/res/layout-port/switcher_popup.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom|left"
+    android:layout_marginLeft="8dip"
+    android:layout_marginBottom="8dip"
+    android:paddingLeft="16dip"
+    android:paddingRight="16dip"
+    android:paddingTop="8dip"
+    android:paddingBottom="8dip"
+    android:background="#80000000" />
diff --git a/res/layout/action_bar_two_line_text.xml b/res/layout/action_bar_two_line_text.xml
index 95faffa..92a4af9 100644
--- a/res/layout/action_bar_two_line_text.xml
+++ b/res/layout/action_bar_two_line_text.xml
@@ -14,10 +14,10 @@
      limitations under the License.
 -->
 <TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/ActionBarTwoLineItem"
         android:layout_height="match_parent"
         android:orientation="vertical"
         android:gravity="center_vertical"
-        android:background="?android:attr/activatedBackgroundIndicator"
         android:duplicateParentState="false"
         android:layout_alignParentLeft="true"
         android:layout_width="wrap_content" >
diff --git a/res/layout/album_content.xml b/res/layout/album_content.xml
new file mode 100644
index 0000000..97509fd
--- /dev/null
+++ b/res/layout/album_content.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <LinearLayout android:id="@+id/progressContainer"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:gravity="center">
+
+        <ProgressBar style="?android:attr/progressBarStyleLarge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        <TextView android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:text="@string/loading"
+                android:paddingTop="4dip"
+                android:singleLine="true" />
+
+    </LinearLayout>
+
+    <FrameLayout android:id="@+id/gridContainer"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <com.android.photos.views.HeaderGridView android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:choiceMode="multipleChoiceModal"
+                android:numColumns="auto_fit"
+                android:stretchMode="columnWidth"
+                android:drawSelectorOnTop="true" />
+        <TextView android:id="@android:id/empty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/album_header.xml b/res/layout/album_header.xml
new file mode 100644
index 0000000..76c9a45
--- /dev/null
+++ b/res/layout/album_header.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/album_header_image"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:scaleType="centerCrop" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:paddingLeft="15dip"
+        android:paddingBottom="10dip"
+        android:paddingTop="20dip"
+        android:background="@drawable/white_text_bg_gradient"
+        android:layout_gravity="bottom"
+        android:orientation="vertical" >
+
+        <TextView
+            android:id="@+id/album_header_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@android:color/white"
+            android:textAppearance="?android:attr/textAppearanceLarge" />
+
+        <TextView
+            android:id="@+id/album_header_subtitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="@android:color/white"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/album_set_item.xml b/res/layout/album_set_item.xml
new file mode 100644
index 0000000..ad0e0db
--- /dev/null
+++ b/res/layout/album_set_item.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:padding="2dp" >
+
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:orientation="vertical"
+        android:padding="10dp"
+        android:background="#FFF" >
+
+        <TextView
+            android:id="@+id/album_set_item_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceMedium" />
+
+        <TextView
+            android:id="@+id/album_set_item_count"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="#AAA" />
+    </LinearLayout>
+
+    <ImageView
+        android:id="@+id/album_set_item_image"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/album_set_item_image_height"
+        android:scaleType="centerCrop" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/bg_replacement_training_message.xml b/res/layout/bg_replacement_training_message.xml
new file mode 100644
index 0000000..8d881d6
--- /dev/null
+++ b/res/layout/bg_replacement_training_message.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/bg_replace_message_frame"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:visibility="gone"
+        android:onClick="onProtectiveCurtainClick"
+        android:background="#77000000">
+    <com.android.camera.ui.RotateLayout
+            android:id="@+id/bg_replace_message"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content"
+            android:layout_centerInParent="true">
+        <LinearLayout
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:orientation="vertical"
+                android:background="@drawable/dialog_full_holo_dark">
+            <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:text="@string/bg_replacement_message"
+                    android:padding="32dp" />
+
+            <View
+                    android:layout_width="match_parent"
+                    android:layout_height="1dp"
+                    android:background="#aaaaaa" />
+
+            <Button android:layout_width="match_parent"
+                    android:layout_height="48dip"
+                    android:layout_gravity="center"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    style="?android:attr/borderlessButtonStyle"
+                    android:text="@android:string/cancel"
+                    android:onClick="onCancelBgTraining"
+                    android:contentDescription="@android:string/cancel" />
+        </LinearLayout>
+    </com.android.camera.ui.RotateLayout>
+</RelativeLayout>
diff --git a/res/layout/camera_main.xml b/res/layout/camera_main.xml
new file mode 100644
index 0000000..99befc0
--- /dev/null
+++ b/res/layout/camera_main.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<com.android.camera.ui.CameraRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <include layout="@layout/gl_root_group" />
+
+    <FrameLayout
+        android:id="@+id/camera_app_root"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <include layout="@layout/camera_controls" />
+
+</com.android.camera.ui.CameraRootView>
\ No newline at end of file
diff --git a/res/layout/count_down_to_capture.xml b/res/layout/count_down_to_capture.xml
new file mode 100644
index 0000000..68276ad
--- /dev/null
+++ b/res/layout/count_down_to_capture.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.camera.ui.CountDownView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/count_down_to_capture"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:visibility="invisible" >
+    <TextView android:id="@+id/remaining_seconds"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:textSize="160sp"
+        android:textColor="@android:color/white"
+        android:gravity="center" />
+    <TextView android:id="@+id/count_down_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:paddingLeft="10dp"
+        android:paddingTop="20dp"
+        android:textSize="20sp"
+        android:textColor="@android:color/white"
+        android:text="@string/count_down_title_text" />
+</com.android.camera.ui.CountDownView>
\ No newline at end of file
diff --git a/res/layout/countdown_setting_popup.xml b/res/layout/countdown_setting_popup.xml
new file mode 100644
index 0000000..22acd92
--- /dev/null
+++ b/res/layout/countdown_setting_popup.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<com.android.camera.ui.CountdownTimerPopup xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingPopupWindow">
+
+    <LinearLayout android:orientation="vertical"
+            android:background="@color/popup_background"
+            android:layout_height="wrap_content"
+            android:layout_width="@dimen/big_setting_popup_window_width">
+
+    <TextView
+            android:id="@+id/title"
+            style="@style/PopupTitleText"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:gravity="center_vertical|center_horizontal"
+            android:minHeight="@dimen/popup_title_frame_min_height" />
+
+    <View style="@style/PopupTitleSeparator" />
+
+    <LinearLayout
+            android:id="@+id/time_duration_picker"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/set_time_interval_title"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:paddingTop="5dip"
+                android:text="@string/set_duration"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+            <!-- A number picker to set timer -->
+
+            <NumberPicker
+                android:id="@+id/duration"
+                android:layout_width="160dp"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginLeft="16dip"
+                android:layout_marginRight="16dip"
+                android:focusable="false" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" >
+
+            <View
+                android:background="#40ffffff"
+                android:layout_width="match_parent"
+                android:layout_height="1dip" />
+            <LinearLayout
+                android:id="@+id/timer_sound"
+                style="@style/SettingRow" >
+
+                <TextView android:id="@+id/beep_title"
+                    style="@style/SettingItemTitle"
+                    android:text="@string/pref_camera_timer_sound_title" />
+
+                <CheckBox android:id="@+id/sound_check_box"
+                    android:layout_gravity="center_vertical|right"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent" />
+            </LinearLayout>
+
+            <View
+                android:background="#40ffffff"
+                android:layout_width="match_parent"
+                android:layout_height="1dip" />
+
+            <Button
+                android:id="@+id/timer_set_button"
+                style="?android:attr/buttonBarButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:text="@string/time_lapse_interval_set"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+        </LinearLayout>
+    </LinearLayout>
+</com.android.camera.ui.CountdownTimerPopup>
diff --git a/res/layout/crop_activity.xml b/res/layout/crop_activity.xml
new file mode 100644
index 0000000..9ff223f
--- /dev/null
+++ b/res/layout/crop_activity.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2012 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/mainView"
+    android:background="@drawable/filtershow_tiled_background">
+
+    <LinearLayout
+        android:id="@+id/mainPanel"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:orientation="vertical" >
+
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" >
+
+            <com.android.gallery3d.filtershow.crop.CropView
+                android:id="@+id/cropView"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+            <ProgressBar
+                android:id="@+id/loading"
+                style="@android:style/Widget.Holo.ProgressBar.Large"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:indeterminate="true"
+                android:indeterminateOnly="true"
+                android:background="@android:color/transparent" />
+
+        </FrameLayout>
+
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/res/layout/cropimage.xml b/res/layout/cropimage.xml
deleted file mode 100644
index c434fb6..0000000
--- a/res/layout/cropimage.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2007 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.
--->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-    <include layout="@layout/gl_root_group"/>
-</FrameLayout>
diff --git a/res/layout/effect_setting_item.xml b/res/layout/effect_setting_item.xml
new file mode 100644
index 0000000..655625c
--- /dev/null
+++ b/res/layout/effect_setting_item.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        tools:ignore="UseCompoundDrawables"
+        style="@style/EffectSettingItem">
+
+        <ImageView android:id="@+id/image"
+                android:layout_height="@dimen/effect_setting_item_icon_width"
+                android:layout_width="@dimen/effect_setting_item_icon_width"
+                android:layout_gravity="center_horizontal"
+                android:scaleType="fitCenter"
+                android:adjustViewBounds="true" />
+        <TextView android:id="@+id/text"
+                style="@style/EffectSettingItemTitle"/>
+</LinearLayout>
diff --git a/res/layout/effect_setting_popup.xml b/res/layout/effect_setting_popup.xml
new file mode 100644
index 0000000..63b7ab4
--- /dev/null
+++ b/res/layout/effect_setting_popup.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<com.android.camera.ui.EffectSettingPopup xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingPopupWindow">
+    <LinearLayout android:orientation="vertical"
+            android:background="@color/popup_background"
+            android:layout_height="wrap_content"
+            android:layout_width="@dimen/big_setting_popup_window_width">
+        <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/popup_title_frame_min_height">
+            <TextView android:id="@+id/title"
+                    style="@style/PopupTitleText" />
+        </FrameLayout>
+        <View style="@style/PopupTitleSeparator" />
+        <ScrollView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            <LinearLayout
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <TextView android:id="@+id/clear_effects"
+                        android:text="@string/clear_effects"
+                        style="@style/EffectSettingTypeTitle"
+                        android:textSize="@dimen/effect_setting_clear_text_size"
+                        android:minHeight="@dimen/effect_setting_clear_text_min_height"
+                        android:background="@drawable/bg_pressed"/>
+                <TextView android:id="@+id/effect_silly_faces_title"
+                        android:text="@string/effect_silly_faces"
+                        android:visibility="gone"
+                        style="@style/EffectSettingTypeTitle"/>
+                <View android:id="@+id/effect_silly_faces_title_separator"
+                        android:visibility="gone"
+                        style="@style/EffectTypeSeparator"/>
+                <com.android.camera.ui.ExpandedGridView android:id="@+id/effect_silly_faces"
+                        style="@style/EffectSettingGrid"/>
+                <View android:id="@+id/effect_background_separator"
+                        android:visibility="gone"
+                        style="@style/EffectTitleSeparator"/>
+                <TextView android:id="@+id/effect_background_title"
+                        android:text="@string/effect_background"
+                        android:visibility="gone"
+                        style="@style/EffectSettingTypeTitle"/>
+                <View android:id="@+id/effect_background_title_separator"
+                        android:visibility="gone"
+                        style="@style/EffectTypeSeparator"/>
+                <com.android.camera.ui.ExpandedGridView android:id="@+id/effect_background"
+                        android:visibility="gone"
+                        style="@style/EffectSettingGrid"/>
+            </LinearLayout>
+        </ScrollView>
+    </LinearLayout>
+</com.android.camera.ui.EffectSettingPopup>
diff --git a/res/layout/face_view.xml b/res/layout/face_view.xml
new file mode 100644
index 0000000..63e7886
--- /dev/null
+++ b/res/layout/face_view.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<com.android.camera.ui.FaceView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
diff --git a/res/layout/filtericonbutton.xml b/res/layout/filtericonbutton.xml
new file mode 100644
index 0000000..8fc98e3
--- /dev/null
+++ b/res/layout/filtericonbutton.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2012 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.
+-->
+<com.android.gallery3d.filtershow.ui.FilterIconButton
+    style="@style/FilterIconButton" />
diff --git a/res/layout/filtershow_actionbar.xml b/res/layout/filtershow_actionbar.xml
index 93c8c59..5f0aa3f 100644
--- a/res/layout/filtershow_actionbar.xml
+++ b/res/layout/filtershow_actionbar.xml
@@ -23,5 +23,5 @@
     android:text="@string/save"
     android:gravity="center_vertical"
     android:textSize="14sp"
-    android:drawableLeft="@drawable/ic_menu_savephoto"
+    android:drawableLeft="@drawable/menu_save_photo"
     android:drawablePadding="8dip" />
\ No newline at end of file
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
index abcb562..f5684ff 100644
--- a/res/layout/filtershow_activity.xml
+++ b/res/layout/filtershow_activity.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2012 The Android Open Source Project
+     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.
@@ -16,479 +16,72 @@
 -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:id="@+id/mainView">
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:id="@+id/mainView"
+             android:background="@drawable/filtershow_tiled_background">
 
     <LinearLayout
-        android:id="@+id/imageStatePanel"
-        android:layout_width="200dip"
-        android:layout_height="match_parent"
-        android:layout_gravity="right"
-        android:orientation="vertical"
-        android:visibility="invisible" >
-
-        <TextView
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@android:color/transparent"
-            android:gravity="center"
-            android:padding="2dip"
-            android:text="@string/imageState"
-            android:textColor="@android:color/white"
-            android:textSize="24sp"
-            android:textStyle="bold" />
+            android:layout_height="match_parent"
+            android:orientation="vertical">
 
-        <ListView
-            android:id="@+id/imageStateList"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" >
-        </ListView>
-    </LinearLayout>
+        <LinearLayout
+                android:layout_weight="1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal">
 
-    <LinearLayout
-        android:id="@+id/mainPanel"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layerType="hardware"
-        android:orientation="vertical" >
-
-        <FrameLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" >
+            <FrameLayout
+                    android:id="@+id/editorContainer"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"/>
 
             <com.android.gallery3d.filtershow.imageshow.ImageShow
-                android:id="@+id/imageShow"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageStraighten
-                android:id="@+id/imageStraighten"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageCrop
-                android:id="@+id/imageCrop"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageRotate
-                android:id="@+id/imageRotate"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageFlip
-                android:id="@+id/imageFlip"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.ui.ImageCurves
-                android:id="@+id/imageCurves"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layerType="software"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageBorder
-                android:id="@+id/imageBorder"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageZoom
-                android:id="@+id/imageZoom"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet
-                android:id="@+id/imageTinyPlanet"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content" />
-
-            <!--
-            <ImageButton
-                android:id="@+id/showOriginalButton"
-                android:layout_width="64dip"
-                android:layout_height="64dip"
-                android:layout_gravity="bottom"
-                android:scaleType="centerInside"
-                android:src="@drawable/filtershow_button_show_original" />
-                 -->
-
-            <com.android.gallery3d.filtershow.CenteredLinearLayout
-              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:layout_gravity="center_horizontal|bottom"
-              android:background="@android:color/transparent"
-              custom:max_width="600dip"
-              android:orientation="vertical">
-
-              <SeekBar
-                  android:id="@+id/filterSeekBar"
-                  android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:layout_gravity="bottom"
-                  android:padding="16dip"
-                  android:visibility="gone" />
-
-            </com.android.gallery3d.filtershow.CenteredLinearLayout>
-
-            <ProgressBar
-                android:id="@+id/loading"
-                style="@android:style/Widget.Holo.ProgressBar.Large"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:indeterminate="true"
-                android:indeterminateOnly="true"
-                android:background="@color/background_screen" />
-
-        </FrameLayout>
-
-        <com.android.gallery3d.filtershow.CenteredLinearLayout
-              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
-              android:id="@+id/filtersPanel"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:layout_gravity="center"
-              android:background="@color/background_main_toolbar"
-              custom:max_width="600dip"
-              android:orientation="vertical">
-
-        <FrameLayout
-            android:id="@+id/secondRowPanel"
-            android:layout_width="fill_parent"
-            android:layout_height="wrap_content" >
-
-            <LinearLayout
-                android:id="@+id/filterButtonsList"
-                android:layout_width="fill_parent"
-                android:layout_height="@dimen/thumbnail_size"
-                android:background="@color/background_main_toolbar"
-                android:orientation="horizontal"
-                android:visibility="gone" >
-
-                <FrameLayout
-                    android:layout_width="fill_parent"
-                    android:layout_height="fill_parent" >
-                    <com.android.gallery3d.filtershow.ui.FramedTextButton
-                        android:id="@+id/aspect"
-                        android:layout_width="84dip"
-                        android:layout_height="84dip"
-                        android:layout_gravity="center_vertical|left"
-                        android:background="@drawable/filtershow_button_background"
-                        android:scaleType="centerInside"
-                        android:visibility="gone"
-                        android:text="@string/aspectNone_effect" />
-
-                    <com.android.gallery3d.filtershow.ui.FramedTextButton
-                        android:id="@+id/pickCurvesChannel"
-                        android:layout_width="84dip"
-                        android:layout_height="84dip"
-                        android:layout_gravity="center_vertical|left"
-                        android:background="@drawable/filtershow_button_background"
-                        android:scaleType="centerInside"
-                        android:visibility="gone"
-                        android:text="@string/curves_channel_rgb" />
-
-                    <Button
-                        android:id="@+id/applyEffect"
-                        android:layout_width="wrap_content"
-                        android:layout_height="94dip"
-                        android:layout_gravity="center"
-                        android:layout_weight="1"
-                        android:background="@android:color/transparent"
-                        android:gravity="center"
-                        android:text="@string/apply_effect"
-                        android:textSize="18dip" />
-                </FrameLayout>
-
-            </LinearLayout>
-
-            <HorizontalScrollView
-                android:id="@+id/fxList"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/thumbnail_size"
-                android:scrollbars="none" >
-
-                <LinearLayout
-                    android:id="@+id/listFilters"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:layout_marginLeft="@dimen/thumbnail_margin"
-                    android:orientation="horizontal" >
-                </LinearLayout>
-            </HorizontalScrollView>
-
-            <HorizontalScrollView
-                android:id="@+id/bordersList"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/thumbnail_size"
-                android:visibility="gone"
-                android:scrollbars="none" >
-
-                <LinearLayout
-                    android:id="@+id/listBorders"
-                    android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
-                    android:layout_marginLeft="@dimen/thumbnail_margin"
-                    android:orientation="horizontal" >
-                </LinearLayout>
-            </HorizontalScrollView>
-
-            <HorizontalScrollView
-                android:id="@+id/geometryList"
-                android:layout_width="fill_parent"
-                android:layout_height="@dimen/thumbnail_size"
-                android:background="@color/background_main_toolbar"
-                android:visibility="gone"
-                android:scrollbars="none" >
-
-                <LinearLayout
-                    android:id="@+id/listGeometry"
-                    android:layout_width="wrap_content"
-                    android:layout_height="fill_parent"
-                    android:layout_gravity="center"
-                    android:orientation="horizontal">
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/straightenButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_geometry_straighten"
-                        android:text="@string/straighten" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/cropButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_geometry_crop"
-                        android:text="@string/crop" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/rotateButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_geometry_rotate"
-                        android:text="@string/rotate" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/flipButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_geometry_flip"
-                        android:text="@string/mirror" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/redEyeButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/photoeditor_effect_redeye"
-                        android:text="@string/redeye"
-                        android:visibility="gone" />
-
-                </LinearLayout>
-            </HorizontalScrollView>
-
-            <HorizontalScrollView
-                android:id="@+id/colorsFxList"
-                android:layout_width="fill_parent"
-                android:layout_height="@dimen/thumbnail_size"
-                android:background="@color/background_main_toolbar"
-                android:visibility="gone"
-                android:scrollbars="none" >
-
-                <LinearLayout
-                    android:id="@+id/listColorsFx"
-                    android:layout_width="wrap_content"
+                    android:id="@+id/imageShow"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:layout_marginLeft="@dimen/thumbnail_margin"
-                    android:orientation="horizontal" >
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/tinyplanetButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/tinyplanet" />
+                    android:layout_weight="1" />
 
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/wbalanceButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/wbalance" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/exposureButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/exposure" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/vignetteButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_vignette"
-                        android:text="@string/vignette" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/contrastButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/contrast" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/shadowRecoveryButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/shadow_recovery" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/vibranceButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/vibrance" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/sharpenButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_sharpen"
-                        android:text="@string/sharpness" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/curvesButtonRGB"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_curve"
-                        android:text="@string/curvesRGB" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/hueButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/hue" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/saturationButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/saturation" />
-
-                    <com.android.gallery3d.filtershow.ui.ImageButtonTitle
-                        android:id="@+id/bwfilterButton"
-                        style="@style/FilterShowBottomButton"
-                        android:src="@drawable/filtershow_button_colors_contrast"
-                        android:text="@string/bwfilter" />
-
-                </LinearLayout>
-            </HorizontalScrollView>
-        </FrameLayout>
-
-        <View
-            android:background="@color/toolbar_separation_line"
-            android:layout_height="1dip"
-            android:layout_width="match_parent" />
+        </LinearLayout>
 
         <com.android.gallery3d.filtershow.CenteredLinearLayout
-              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"
-              android:layout_gravity="center"
-              custom:max_width="400dip"
-              android:orientation="vertical">
+                xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+                android:id="@+id/mainPanel"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center|bottom"
+                custom:max_width="650dip"
+                android:orientation="vertical" >
 
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="48dip"
-            android:background="@color/background_main_toolbar" >
+            <FrameLayout android:id="@+id/main_panel_container"
+                         android:layout_gravity="center"
+                         android:layout_width="match_parent"
+                         android:layout_height="0dip"
+                         android:layout_weight="1" />
 
-            <ImageButton
-                android:id="@+id/fxButton"
-                android:layout_width="@dimen/thumbnail_size"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="@drawable/filtershow_button_background"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_photoeditor_effects" />
+            <FrameLayout
+                    android:layout_gravity="bottom"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:visibility="gone">
 
-            <ImageButton
-                android:id="@+id/borderButton"
-                android:layout_width="@dimen/thumbnail_size"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="@drawable/filtershow_button_background"
-                android:padding="2dip"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_photoeditor_border" />
 
-            <ImageButton
-                android:id="@+id/geometryButton"
-                android:layout_width="@dimen/thumbnail_size"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="@drawable/filtershow_button_background"
-                android:padding="2dip"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_photoeditor_fix" />
+                <ProgressBar
+                        android:id="@+id/loading"
+                        style="@android:style/Widget.Holo.ProgressBar.Large"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:layout_gravity="center"
+                        android:indeterminate="true"
+                        android:indeterminateOnly="true"
+                        android:background="@color/background_screen"/>
 
-            <ImageButton
-                android:id="@+id/colorsButton"
-                android:layout_width="@dimen/thumbnail_size"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:background="@drawable/filtershow_button_background"
-                android:padding="2dip"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_photoeditor_color" />
-        </LinearLayout>
+            </FrameLayout>
 
         </com.android.gallery3d.filtershow.CenteredLinearLayout>
 
-        </com.android.gallery3d.filtershow.CenteredLinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:id="@+id/historyPanel"
-        android:layout_width="200dip"
-        android:layout_height="match_parent"
-        android:layout_gravity="right"
-        android:orientation="vertical"
-        android:visibility="invisible" >
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@android:color/transparent"
-            android:gravity="center"
-            android:padding="2dip"
-            android:text="@string/history"
-            android:textColor="@android:color/white"
-            android:textSize="24sp"
-            android:textStyle="bold" />
-
-        <ListView
-            android:id="@+id/operationsList"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1" >
-        </ListView>
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal" >
-
-            <Button
-                android:id="@+id/resetOperationsButton"
-                style="@style/FilterShowHistoryButton"
-                android:gravity="center"
-                android:text="@string/reset" />
-
-            <Button
-                android:id="@+id/saveOperationsButton"
-                style="@style/FilterShowHistoryButton"
-                android:text="@string/save"
-                android:visibility="gone" />
-        </LinearLayout>
     </LinearLayout>
 
 </FrameLayout>
diff --git a/res/layout/filtershow_category_panel_new.xml b/res/layout/filtershow_category_panel_new.xml
new file mode 100644
index 0000000..0dce498
--- /dev/null
+++ b/res/layout/filtershow_category_panel_new.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+              android:orientation="horizontal"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content">
+
+    <HorizontalScrollView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbars="none"
+            android:background="@color/background_main_toolbar" >
+
+        <com.android.gallery3d.filtershow.category.CategoryTrack
+                android:id="@+id/listItems"
+                android:layout_width="match_parent"
+                android:layout_height="128dip"
+                custom:iconSize="84dip"
+                android:divider="@android:color/transparent"
+                android:dividerPadding="8dip"
+                />
+
+    </HorizontalScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_color_gird.xml b/res/layout/filtershow_color_gird.xml
new file mode 100644
index 0000000..2dbbc5f
--- /dev/null
+++ b/res/layout/filtershow_color_gird.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/textView1"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <TableLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+
+        <TableRow
+            android:id="@+id/tableRow1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" >
+
+            <Button
+                android:id="@+id/cp_grid_button01"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button2"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button03"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button04"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button05"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+        </TableRow>
+
+        <TableRow
+            android:id="@+id/tableRow2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+
+            <Button
+                android:id="@+id/Button06"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button07"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button08"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button09"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button10"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+        </TableRow>
+
+        <TableRow
+            android:id="@+id/tableRow3"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+
+            <Button
+                android:id="@+id/Button11"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button12"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button13"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button14"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button15"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+        </TableRow>
+
+        <TableRow
+            android:id="@+id/tableRow4"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+
+            <Button
+                android:id="@+id/Button16"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button17"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button18"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button19"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+
+            <Button
+                android:id="@+id/button20"
+                android:layout_width="0dip"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:background="@drawable/filtershow_color_picker_circle" />
+        </TableRow>
+    </TableLayout>
+
+    <Button
+        android:id="@+id/filtershow_cp_custom"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/color_pick_select" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_color_picker.xml b/res/layout/filtershow_color_picker.xml
new file mode 100644
index 0000000..fc49729
--- /dev/null
+++ b/res/layout/filtershow_color_picker.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/RelativeLayout1"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/default_background"
+    tools:context=".ColorPickerActivity" >
+
+    <com.android.gallery3d.filtershow.colorpicker.ColorRectView
+        android:id="@+id/colorRectView"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:layout_marginLeft="10dp"
+        android:layout_marginTop="10dp"
+        android:layout_marginBottom="10dp"
+        android:layout_marginRight="1dp"
+        android:layout_above="@+id/colorOpacityView"
+        android:layout_toLeftOf="@+id/colorValueView" />
+
+    <com.android.gallery3d.filtershow.colorpicker.ColorValueView
+        android:id="@+id/colorValueView"
+               android:layout_width="90dp"
+        android:layout_height="fill_parent"
+        android:layout_alignParentRight = "true"
+        android:layout_above="@+id/colorOpacityView"  />
+
+    <com.android.gallery3d.filtershow.colorpicker.ColorOpacityView
+        android:id="@+id/colorOpacityView"
+       android:layout_width="match_parent"
+        android:layout_height="90dp"
+         android:layout_above="@+id/btnSelect" />
+
+    <Button
+        android:id="@+id/btnSelect"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/color_pick_select"
+        android:layout_alignParentBottom = "true"
+        />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_control_action_slider.xml b/res/layout/filtershow_control_action_slider.xml
new file mode 100644
index 0000000..a3ef3ed
--- /dev/null
+++ b/res/layout/filtershow_control_action_slider.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res/com.example.imagefilterharness"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal" >
+
+    <ImageButton
+        android:id="@+id/leftActionButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="left|center_vertical"
+        android:scaleType="centerInside"
+        android:layout_weight="0"
+        android:background="@drawable/filtershow_button_background"
+        android:src="@drawable/filtershow_addpoint"
+        android:paddingBottom="8dp"  />
+
+    <SeekBar
+        android:id="@+id/controlValueSeekBar"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        style="@style/FilterShowSlider" />
+
+    <ImageButton
+        android:id="@+id/rightActionButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="left|center_vertical"
+        android:scaleType="centerInside"
+        android:layout_weight="0"
+        android:background="@drawable/filtershow_button_background"
+        android:src="@drawable/ic_menu_trash_holo_light"
+        android:paddingBottom="8dp"  />
+
+</LinearLayout>
+
diff --git a/res/layout/filtershow_control_style_chooser.xml b/res/layout/filtershow_control_style_chooser.xml
new file mode 100644
index 0000000..a5bc984
--- /dev/null
+++ b/res/layout/filtershow_control_style_chooser.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res/com.example.imagefilterharness"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal" >
+            <HorizontalScrollView
+                android:id="@+id/scrollList"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scrollbars="none" >
+
+                <LinearLayout
+                    android:id="@+id/listStyles"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:orientation="horizontal" >
+                </LinearLayout>
+            </HorizontalScrollView>
+</LinearLayout>
+
diff --git a/res/layout/filtershow_control_title_slider.xml b/res/layout/filtershow_control_title_slider.xml
new file mode 100644
index 0000000..584e015
--- /dev/null
+++ b/res/layout/filtershow_control_title_slider.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:columnCount="2"
+    android:orientation="horizontal" >
+
+    <TextView
+        android:id="@+id/controlName"
+        android:layout_gravity="left"
+        android:layout_marginLeft="8dip" />
+
+    <TextView
+        android:id="@+id/controlValue"
+        android:layout_gravity="right"
+        android:layout_marginRight="8dip"
+        android:textStyle="bold" />
+
+    <SeekBar
+        android:id="@+id/controlValueSeekBar"
+        android:layout_width="match_parent"
+        android:layout_column="0"
+        android:layout_columnSpan="2"
+        android:layout_gravity="fill_horizontal"
+        style="@style/FilterShowSlider" />
+</GridLayout>
+
diff --git a/res/layout/filtershow_default_editor.xml b/res/layout/filtershow_default_editor.xml
new file mode 100644
index 0000000..b261ea3
--- /dev/null
+++ b/res/layout/filtershow_default_editor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:iconbutton="http://schemas.android.com/apk/res/com.android.gallery3d"
+    android:id="@+id/basicEditor"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_weight="1" >
+
+    <com.android.gallery3d.filtershow.imageshow.ImageShow
+        android:id="@+id/imageShow"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+ </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_draw_size.xml b/res/layout/filtershow_draw_size.xml
new file mode 100644
index 0000000..068493e
--- /dev/null
+++ b/res/layout/filtershow_draw_size.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:dividerPadding="20dp"
+    android:gravity="center_horizontal"
+    android:orientation="vertical" >
+
+    <SeekBar
+        android:id="@+id/sizeSeekBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+    <Button
+        android:id="@+id/sizeAcceptButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/draw_size_accept" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_editor_panel.xml b/res/layout/filtershow_editor_panel.xml
new file mode 100644
index 0000000..a6da46a
--- /dev/null
+++ b/res/layout/filtershow_editor_panel.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/top"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:visibility="visible" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+                android:id="@+id/controlArea"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                android:layout_alignParentBottom="true"
+                android:visibility="visible">
+
+            <SeekBar
+                    android:id="@+id/primarySeekBar"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    style="@style/FilterShowSlider"/>
+
+        </LinearLayout>
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="56dip"
+                android:background="@color/background_main_toolbar"
+                android:orientation="horizontal"
+                android:baselineAligned="false"
+                android:visibility="visible">
+
+            <ImageButton
+                    android:id="@+id/cancelFilter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_gravity="left|center_vertical"
+                    android:background="@android:color/transparent"
+                    android:layout_weight=".1"
+                    android:gravity="center"
+                    android:src="@drawable/ic_menu_cancel_holo_light"
+                    android:textSize="18dip"/>
+
+            <ImageView
+                    android:layout_width="2dp"
+                    android:layout_height="fill_parent"
+                    android:src="@drawable/filtershow_vertical_bar"/>
+
+            <LinearLayout
+                    android:id="@+id/panelAccessoryViewList"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:orientation="horizontal"
+                    android:visibility="visible">
+
+                <com.android.gallery3d.filtershow.editors.SwapButton
+                        android:id="@+id/applyEffect"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_gravity="center"
+                        android:background="@android:color/transparent"
+                        android:gravity="center"
+                        android:text="@string/apply_effect"
+                        android:textSize="18dip"
+                        android:drawableRight="@drawable/filtershow_menu_marker"
+                        android:textAllCaps="true" />
+
+            </LinearLayout>
+
+            <ImageView
+                    android:layout_width="2dp"
+                    android:layout_height="fill_parent"
+                    android:src="@drawable/filtershow_vertical_bar"/>
+
+            <ImageButton
+                    android:id="@+id/applyFilter"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_gravity="right|center_vertical"
+                    android:layout_weight=".1"
+                    android:background="@android:color/transparent"
+                    android:gravity="center"
+                    android:src="@drawable/ic_menu_done_holo_light"
+                    android:textSize="18dip"/>
+        </LinearLayout>
+
+        <FrameLayout android:id="@+id/state_panel_container"
+                     android:layout_width="match_parent"
+                     android:layout_height="wrap_content"
+                     android:visibility="visible" />
+
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/res/layout/filtershow_history_operation_row.xml b/res/layout/filtershow_history_operation_row.xml
index dd9b66e..25a0d26 100644
--- a/res/layout/filtershow_history_operation_row.xml
+++ b/res/layout/filtershow_history_operation_row.xml
@@ -17,41 +17,31 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="120dip"
+    android:gravity="center_horizontal"
     android:orientation="horizontal"
-    android:background="@drawable/filtershow_button_background">
+    android:padding="0dip"
+    android:background="@color/background_main_toolbar">
 
     <ImageView
-        android:id="@+id/selectedMark"
-        android:src="@drawable/camera_crop"
-        android:background="@android:color/transparent"
-        android:layout_width="32dip"
-        android:layout_height="match_parent"
-        android:scaleType="centerInside"
-        android:visibility="invisible"
-        >
-    </ImageView>
+            android:id="@+id/preview"
+            android:layout_width="180dip"
+            android:layout_height="120dip"
+            android:scaleType="centerCrop"
+            android:cropToPadding="true"
+            android:visibility="visible"
+            />
 
     <TextView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/rowTextView"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:padding="10dip"
-        android:textSize="16dip" >
+            xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/rowTextView"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="bottom|right"
+            android:padding="10dip"
+            android:textSize="16dip"
+            android:textStyle="bold">
     </TextView>
 
-    <ImageView
-        android:id="@+id/typeMark"
-        android:src="@drawable/filtershow_button_origin"
-        android:background="@android:color/transparent"
-        android:layout_width="32dip"
-        android:layout_height="match_parent"
-        android:scaleType="centerInside"
-        android:paddingRight="4dip"
-        android:visibility="visible"
-        >
-    </ImageView>
-
 </LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_imagestate_row.xml b/res/layout/filtershow_imagestate_row.xml
deleted file mode 100644
index 2e9b1bf..0000000
--- a/res/layout/filtershow_imagestate_row.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:background="@drawable/filtershow_button_background">
-
-    <TextView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/imagestate_label"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:gravity="left"
-        android:padding="10dip"
-        android:textSize="16dip" >
-    </TextView>
-
-    <TextView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/imagestate_parameter"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:gravity="right"
-        android:padding="10dip"
-        android:textSize="16dip"
-        android:textStyle="bold" >
-    </TextView>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_main_panel.xml b/res/layout/filtershow_main_panel.xml
new file mode 100644
index 0000000..53691d3
--- /dev/null
+++ b/res/layout/filtershow_main_panel.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:baselineAligned="false"
+              android:orientation="vertical"
+              android:animateLayoutChanges="false"
+              android:visibility="visible"
+              android:background="@color/background_main_toolbar" >
+
+    <FrameLayout android:id="@+id/state_panel_container"
+                 android:layout_width="match_parent"
+                 android:layout_height="wrap_content"
+                 android:visibility="visible"
+                 android:layout_gravity="top"
+                 android:layout_weight="1" />
+
+    <FrameLayout android:id="@+id/category_panel_container"
+                 android:layout_width="wrap_content"
+                 android:visibility="visible"
+                 android:layout_height="0dip"
+                 android:layout_gravity="center"
+                 android:layout_weight="1"/>
+
+    <View
+            android:background="@color/toolbar_separation_line"
+            android:layout_height="1dip"
+            android:layout_width="match_parent"/>
+
+    <com.android.gallery3d.filtershow.CenteredLinearLayout
+            xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+            android:id="@+id/bottom_panel"
+            android:layout_width="match_parent"
+            android:layout_height="48dip"
+            android:layout_gravity="center|bottom"
+            custom:max_width="400dip"
+            android:orientation="vertical">
+
+        <LinearLayout android:layout_width="wrap_content"
+                      android:layout_height="match_parent"
+                      android:background="@color/background_main_toolbar">
+
+            <ImageButton
+                    android:id="@+id/fxButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_effects"/>
+
+            <ImageButton
+                    android:id="@+id/borderButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_border"/>
+
+            <ImageButton
+                    android:id="@+id/geometryButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_fix"/>
+
+            <ImageButton
+                    android:id="@+id/colorsButton"
+                    android:layout_width="@dimen/thumbnail_size"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"
+                    android:background="@drawable/filtershow_button_background"
+                    android:padding="2dip"
+                    android:scaleType="centerInside"
+                    android:src="@drawable/ic_photoeditor_color"/>
+
+        </LinearLayout>
+
+    </com.android.gallery3d.filtershow.CenteredLinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_seekbar.xml b/res/layout/filtershow_seekbar.xml
new file mode 100644
index 0000000..6463ca8
--- /dev/null
+++ b/res/layout/filtershow_seekbar.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/top"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:visibility="visible" >
+
+    <SeekBar
+        android:id="@+id/primarySeekBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/FilterShowSlider" />
+
+</LinearLayout>
diff --git a/res/layout/filtershow_state_panel_new.xml b/res/layout/filtershow_state_panel_new.xml
new file mode 100644
index 0000000..d2d59ab
--- /dev/null
+++ b/res/layout/filtershow_state_panel_new.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:custom="http://schemas.android.com/apk/res/com.android.gallery3d"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="@color/background_main_toolbar">
+
+    <View
+            android:background="@color/toolbar_separation_line"
+            android:layout_height="1dip"
+            android:layout_width="match_parent"/>
+
+    <HorizontalScrollView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbars="none">
+
+        <com.android.gallery3d.filtershow.state.StatePanelTrack
+                android:id="@+id/listStates"
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="48dip"
+                custom:elemEndSize="128dip"
+                custom:elemSize="128dip"
+                android:layout_margin="8dip"
+                android:animateLayoutChanges="true" />
+
+    </HorizontalScrollView>
+
+</LinearLayout>
diff --git a/res/layout/filtershow_tiny_planet_editor.xml b/res/layout/filtershow_tiny_planet_editor.xml
new file mode 100644
index 0000000..fd89f99
--- /dev/null
+++ b/res/layout/filtershow_tiny_planet_editor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:iconbutton="http://schemas.android.com/apk/res/com.android.gallery3d"
+    android:id="@+id/tinyPlanetEditor"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_weight="1" >
+
+    <com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet
+        android:id="@+id/imageTinyPlanet"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+ </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_vignette_editor.xml b/res/layout/filtershow_vignette_editor.xml
new file mode 100644
index 0000000..9c9b4cb
--- /dev/null
+++ b/res/layout/filtershow_vignette_editor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:iconbutton="http://schemas.android.com/apk/res/com.android.gallery3d"
+    android:id="@+id/vignetteEditor"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_weight="1" >
+
+    <com.android.gallery3d.filtershow.imageshow.ImageVignette
+        android:id="@+id/imageVignette"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+ </FrameLayout>
\ No newline at end of file
diff --git a/res/layout/filtershow_zoom_editor.xml b/res/layout/filtershow_zoom_editor.xml
new file mode 100644
index 0000000..9813a28
--- /dev/null
+++ b/res/layout/filtershow_zoom_editor.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:iconbutton="http://schemas.android.com/apk/res/com.android.gallery3d"
+    android:id="@+id/basicEditor"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_weight="1" >
+
+    <com.android.gallery3d.filtershow.imageshow.ImageShow
+        android:id="@+id/imageZoom"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+ </FrameLayout>
diff --git a/res/layout/in_line_setting_check_box.xml b/res/layout/in_line_setting_check_box.xml
new file mode 100644
index 0000000..a4d9bba
--- /dev/null
+++ b/res/layout/in_line_setting_check_box.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<com.android.camera.ui.InLineSettingCheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingRow">
+    <TextView android:id="@+id/title"
+            style="@style/SettingItemTitle" />
+
+    <!-- The Switch widget always aligns to the right, so we have to wrap it in a frame layout. -->
+    <FrameLayout
+            android:layout_width="@dimen/setting_item_text_width"
+            android:layout_height="match_parent">
+        <CheckBox android:id="@+id/setting_check_box"
+                android:layout_gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent" />
+    </FrameLayout>
+</com.android.camera.ui.InLineSettingCheckBox>
diff --git a/res/layout/in_line_setting_menu.xml b/res/layout/in_line_setting_menu.xml
new file mode 100644
index 0000000..f45f10f
--- /dev/null
+++ b/res/layout/in_line_setting_menu.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<com.android.camera.ui.InLineSettingMenu xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingRow"
+        android:background="@drawable/bg_pressed_exit_fading">
+    <TextView android:id="@+id/title"
+            style="@style/SettingItemTitle" />
+
+    <TextView android:id="@+id/current_setting"
+            style="@style/SettingItemText" />
+
+</com.android.camera.ui.InLineSettingMenu>
+
diff --git a/res/layout/ingest_activity_item_list.xml b/res/layout/ingest_activity_item_list.xml
new file mode 100644
index 0000000..f0e91e8
--- /dev/null
+++ b/res/layout/ingest_activity_item_list.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <com.android.gallery3d.ingest.ui.IngestGridView
+        android:id="@+id/ingest_gridview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:columnWidth="120dip"
+        android:numColumns="auto_fit"
+        android:fastScrollEnabled="true"
+        android:background="@android:color/background_dark"
+        android:choiceMode="multipleChoiceModal"
+        android:stretchMode="columnWidth"  />
+
+    <android.support.v4.view.ViewPager
+        android:id="@+id/ingest_view_pager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@android:color/background_dark"
+        android:visibility="invisible" />
+
+    <LinearLayout
+        android:id="@+id/ingest_warning_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="20dip"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:visibility="invisible" >
+
+        <ImageView
+            android:id="@+id/ingest_warning_view_icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:src="@android:drawable/ic_dialog_alert" />
+
+        <TextView
+            android:id="@+id/ingest_warning_view_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dip"
+            android:textAppearance="?android:attr/textAppearanceSmall" />
+    </LinearLayout>
+</merge>
+
diff --git a/res/layout/ingest_date_tile.xml b/res/layout/ingest_date_tile.xml
new file mode 100644
index 0000000..6b5e934
--- /dev/null
+++ b/res/layout/ingest_date_tile.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.gallery3d.ingest.ui.DateTileView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/black" >
+    <GridLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center" >
+        <TextView
+            android:id="@+id/date_tile_month"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_column="0"
+            android:layout_row="0"
+            android:layout_gravity="bottom|right"
+            android:layout_marginTop="7sp"
+            android:includeFontPadding="false"
+            android:textSize="16sp"
+            android:textAllCaps="true"
+            android:fontFamily="sans-serif"
+            android:textColor="@color/ingest_date_tile_text" />
+        <TextView
+            android:id="@+id/date_tile_year"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_column="0"
+            android:layout_row="1"
+            android:layout_gravity="top|right"
+            android:includeFontPadding="false"
+            android:textSize="13sp"
+            android:fontFamily="sans-serif-light"
+            android:textColor="@color/ingest_date_tile_text" />
+        <TextView
+            android:id="@+id/date_tile_day"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_column="1"
+            android:layout_row="0"
+            android:layout_rowSpan="2"
+            android:layout_gravity="top|left"
+            android:layout_marginLeft="5sp"
+            android:includeFontPadding="false"
+            android:textSize="44sp"
+            android:textStyle="bold"
+            android:fontFamily="sans-serif"
+            android:textColor="@color/ingest_date_tile_text" />
+    </GridLayout>
+</com.android.gallery3d.ingest.ui.DateTileView>
\ No newline at end of file
diff --git a/res/layout/ingest_fullsize.xml b/res/layout/ingest_fullsize.xml
new file mode 100644
index 0000000..fad596c
--- /dev/null
+++ b/res/layout/ingest_fullsize.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.gallery3d.ingest.ui.MtpFullscreenView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <ProgressBar
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true"
+        android:layout_centerVertical="true"
+        android:progress="1"
+        android:indeterminate="true"
+        android:indeterminateOnly="true" />
+
+    <com.android.gallery3d.ingest.ui.MtpImageView
+        android:id="@+id/ingest_fullsize_image"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:scaleType="matrix" />
+
+    <CheckBox
+        android:id="@+id/ingest_fullsize_image_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentRight="true"
+        android:text="@string/Import" />
+
+</com.android.gallery3d.ingest.ui.MtpFullscreenView>
\ No newline at end of file
diff --git a/res/layout/ingest_thumbnail.xml b/res/layout/ingest_thumbnail.xml
new file mode 100644
index 0000000..6907149
--- /dev/null
+++ b/res/layout/ingest_thumbnail.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.gallery3d.ingest.ui.MtpThumbnailTileView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:scaleType="centerCrop"
+    android:background="@drawable/ingest_item_list_selector">
+</com.android.gallery3d.ingest.ui.MtpThumbnailTileView>
\ No newline at end of file
diff --git a/res/layout/list_pref_setting_popup.xml b/res/layout/list_pref_setting_popup.xml
new file mode 100644
index 0000000..5bfaa52
--- /dev/null
+++ b/res/layout/list_pref_setting_popup.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011, 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.
+-->
+<com.android.camera.ui.ListPrefSettingPopup xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingPopupWindow">
+
+    <LinearLayout android:orientation="vertical"
+            android:background="@color/popup_background"
+            android:layout_height="wrap_content"
+            android:layout_width="@dimen/setting_popup_window_width">
+
+        <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minHeight="@dimen/popup_title_frame_min_height">
+            <TextView android:id="@+id/title"
+                    style="@style/PopupTitleText" />
+        </FrameLayout>
+
+        <View style="@style/PopupTitleSeparator" />
+
+        <FrameLayout android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+            <ListView android:id="@+id/settingList"
+                    style="@style/SettingItemList"
+                    android:choiceMode="singleChoice" />
+        </FrameLayout>
+    </LinearLayout>
+</com.android.camera.ui.ListPrefSettingPopup>
diff --git a/res/layout/menu_indicators.xml b/res/layout/menu_indicators.xml
new file mode 100644
index 0000000..0377003
--- /dev/null
+++ b/res/layout/menu_indicators.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/on_screen_indicators"
+    android:layout_width="64dip"
+    android:layout_height="64dip" >
+
+    <ImageView
+        android:id="@+id/menu_scenemode_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="left|top"
+        android:src="@drawable/ic_indicator_sce_off" />
+
+    <ImageView
+        android:id="@+id/menu_timer_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="center_horizontal|top"
+        android:src="@drawable/ic_indicator_timer_off" />
+
+    <ImageView
+        android:id="@+id/menu_flash_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="right|top"
+        android:src="@drawable/ic_indicator_flash_off" />
+
+    <ImageView
+        android:id="@+id/menu_exposure_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="left|bottom"
+        android:src="@drawable/ic_indicator_ev_0" />
+
+    <ImageView
+        android:id="@+id/menu_location_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="center_horizontal|bottom"
+        android:src="@drawable/ic_indicator_loc_on" />
+
+    <ImageView
+        android:id="@+id/menu_wb_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="right|bottom"
+        android:src="@drawable/ic_indicator_wb_off" />
+
+</FrameLayout>
diff --git a/res/layout/menu_indicators_keyguard.xml b/res/layout/menu_indicators_keyguard.xml
new file mode 100644
index 0000000..7a8795d
--- /dev/null
+++ b/res/layout/menu_indicators_keyguard.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/on_screen_indicators"
+    android:layout_width="64dip"
+    android:layout_height="64dip" >
+
+    <ImageView
+        android:id="@+id/menu_scenemode_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="left|top"
+        android:src="@drawable/ic_indicator_sce_off" />
+
+    <ImageView
+        android:id="@+id/menu_timer_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="center_horizontal|top"
+        android:src="@drawable/ic_indicator_timer_off" />
+
+    <ImageView
+        android:id="@+id/menu_flash_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="right|top"
+        android:src="@drawable/ic_indicator_flash_auto" />
+
+    <ImageView
+        android:id="@+id/menu_exposure_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="left|bottom"
+        android:src="@drawable/ic_indicator_ev_0" />
+
+    <ImageView
+        android:id="@+id/menu_location_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="center_horizontal|bottom"
+        android:src="@drawable/ic_indicator_loc_on" />
+
+    <ImageView
+        android:id="@+id/menu_wb_indicator"
+        style="@style/MenuIndicator"
+        android:layout_gravity="right|bottom"
+        android:src="@drawable/ic_indicator_wb_off" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/more_setting_popup.xml b/res/layout/more_setting_popup.xml
new file mode 100644
index 0000000..3ccde85
--- /dev/null
+++ b/res/layout/more_setting_popup.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2010, 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.
+-->
+<com.android.camera.ui.MoreSettingPopup xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingPopupWindow">
+
+    <FrameLayout
+            android:background="@color/popup_background"
+            android:layout_width="@dimen/big_setting_popup_window_width"
+            android:layout_height="wrap_content">
+        <ListView android:id="@+id/settingList"
+                style="@style/SettingItemList" />
+    </FrameLayout>
+</com.android.camera.ui.MoreSettingPopup>
diff --git a/res/layout/multigrid_content.xml b/res/layout/multigrid_content.xml
new file mode 100644
index 0000000..b1cb145
--- /dev/null
+++ b/res/layout/multigrid_content.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <LinearLayout android:id="@+id/progressContainer"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:gravity="center">
+
+        <ProgressBar style="?android:attr/progressBarStyleLarge"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+        <TextView android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:text="@string/loading"
+                android:paddingTop="4dip"
+                android:singleLine="true" />
+
+    </LinearLayout>
+
+    <FrameLayout android:id="@+id/gridContainer"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <GridView android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:choiceMode="multipleChoiceModal"
+                android:numColumns="auto_fit"
+                android:stretchMode="columnWidth"
+                android:drawSelectorOnTop="true" />
+        <TextView android:id="@android:id/empty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:gravity="center"
+                android:textAppearance="?android:attr/textAppearanceMedium" />
+    </FrameLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/panorama_module.xml b/res/layout/panorama_module.xml
new file mode 100644
index 0000000..9ecbd07
--- /dev/null
+++ b/res/layout/panorama_module.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/pano_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <include layout="@layout/pano_module_capture" />
+    <include layout="@layout/pano_review" />
+</merge>
diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml
new file mode 100644
index 0000000..abf094e
--- /dev/null
+++ b/res/layout/photo_module.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This layout is shared by phone and tablet in both landscape and portrait
+ orientation. The purpose of having this layout is to eventually not manually
+ recreate views when the orientation changes, by migrating the views that do not
+ need to be recreated in onConfigurationChanged from old photo_module to this
+ layout. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center">
+    <include layout="@layout/count_down_to_capture"/>
+
+    <ViewStub android:id="@+id/face_view_stub"
+        android:inflatedId="@+id/face_view"
+        android:layout="@layout/face_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
+    <com.android.camera.ui.RenderOverlay
+        android:id="@+id/render_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</merge>
\ No newline at end of file
diff --git a/res/layout/photo_set_item.xml b/res/layout/photo_set_item.xml
new file mode 100644
index 0000000..0f740fa
--- /dev/null
+++ b/res/layout/photo_set_item.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/activatedBackgroundIndicator"
+    android:padding="2dip">
+
+    <com.android.photos.views.SquareImageView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/thumbnail" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/layout/photoeditor_actionbar.xml b/res/layout/photoeditor_actionbar.xml
deleted file mode 100644
index c82faca..0000000
--- a/res/layout/photoeditor_actionbar.xml
+++ /dev/null
@@ -1,67 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/ActionBarInner"
-    android:background="@drawable/photoeditor_actionbar_translucent">
-
-    <LinearLayout style="@style/ActionBarLinearLayout">
-
-        <LinearLayout
-            android:id="@+id/action_bar_back"
-            style="@style/ActionBarBackLinearLayout">
-            <ImageView style="@style/ActionBarArrow"/>
-            <ImageView style="@style/ActionBarIcon"/>
-        </LinearLayout>
-
-        <TextView
-            android:id="@+id/action_bar_title"
-            style="@style/ActionBarTitle"
-            android:text="@string/edit"/>
-    </LinearLayout>
-
-    <LinearLayout style="@style/ActionBarLinearLayout" android:layout_alignParentRight="true">
-
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/undo_button"
-            style="@style/ImageActionButton"
-            android:src="@drawable/photoeditor_undo"
-            android:contentDescription="@string/photoeditor_undo"/>
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/redo_button"
-            style="@style/ImageActionButton"
-            android:src="@drawable/photoeditor_redo"
-            android:contentDescription="@string/photoeditor_redo"/>
-
-        <ViewSwitcher
-            android:id="@+id/save_share_buttons"
-            android:layout_width="wrap_content"
-            android:layout_height="fill_parent">
-            <Button
-                android:id="@+id/save_button"
-                style="@style/TextActionButton"
-                android:layout_width="fill_parent"
-                android:text="@string/save"/>
-            <com.android.gallery3d.photoeditor.ImageActionButton
-                android:id="@+id/share_button"
-                style="@style/ImageActionButton"
-                android:layout_width="fill_parent"
-                android:src="@drawable/ic_menu_share_holo_light"/>
-        </ViewSwitcher>
-
-    </LinearLayout>
-</RelativeLayout>
diff --git a/res/layout/photoeditor_color_seekbar.xml b/res/layout/photoeditor_color_seekbar.xml
deleted file mode 100644
index ca8509d..0000000
--- a/res/layout/photoeditor_color_seekbar.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.ColorSeekBar
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/SeekBar"/>
diff --git a/res/layout/photoeditor_crop_view.xml b/res/layout/photoeditor_crop_view.xml
deleted file mode 100644
index bf5cacb..0000000
--- a/res/layout/photoeditor_crop_view.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.CropView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_doodle_view.xml b/res/layout/photoeditor_doodle_view.xml
deleted file mode 100644
index c202f14..0000000
--- a/res/layout/photoeditor_doodle_view.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.DoodleView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_effect_tool_fullscreen.xml b/res/layout/photoeditor_effect_tool_fullscreen.xml
deleted file mode 100644
index a6dd323..0000000
--- a/res/layout/photoeditor_effect_tool_fullscreen.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"/>
diff --git a/res/layout/photoeditor_effect_tool_panel.xml b/res/layout/photoeditor_effect_tool_panel.xml
deleted file mode 100644
index 4ffd52d..0000000
--- a/res/layout/photoeditor_effect_tool_panel.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:paddingTop="@dimen/effect_tool_panel_padding_top"
-    android:paddingBottom="@dimen/effect_tool_panel_padding_bottom"
-    android:background="@color/translucent_black"
-    android:gravity="center_horizontal"
-    android:orientation="vertical">
-
-    <TextView
-        android:id="@+id/effect_label"
-        style="@style/EffectLabelInToolPanel"/>
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_artistic.xml b/res/layout/photoeditor_effects_artistic.xml
deleted file mode 100644
index 89cb88c..0000000
--- a/res/layout/photoeditor_effects_artistic.xml
+++ /dev/null
@@ -1,85 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/EffectsContainer">
-
-    <com.android.gallery3d.photoeditor.actions.CrossProcessAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_crossprocess"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/crossprocess"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.CrossProcessAction>
-    <com.android.gallery3d.photoeditor.actions.PosterizeAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_posterize"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/posterize"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.PosterizeAction>
-    <com.android.gallery3d.photoeditor.actions.LomoishAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_lomoish"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/lomoish"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.LomoishAction>
-    <com.android.gallery3d.photoeditor.actions.DocumentaryAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_documentary"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/documentary"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.DocumentaryAction>
-    <com.android.gallery3d.photoeditor.actions.VignetteAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_vignette"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/vignette"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.VignetteAction>
-    <com.android.gallery3d.photoeditor.actions.GrainAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_grain"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/grain"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.GrainAction>
-    <com.android.gallery3d.photoeditor.actions.FisheyeAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_fisheye"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/fisheye"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.FisheyeAction>
-
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_color.xml b/res/layout/photoeditor_effects_color.xml
deleted file mode 100644
index a2a88b2..0000000
--- a/res/layout/photoeditor_effects_color.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/EffectsContainer">
-
-    <com.android.gallery3d.photoeditor.actions.ColorTemperatureAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_temperature"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/temperature"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.ColorTemperatureAction>
-    <com.android.gallery3d.photoeditor.actions.SaturationAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_saturation"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/saturation"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.SaturationAction>
-    <com.android.gallery3d.photoeditor.actions.GrayscaleAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_grayscale"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/grayscale"
-            android:contentDescription="@string/accessibility_black_and_white"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.GrayscaleAction>
-    <com.android.gallery3d.photoeditor.actions.SepiaAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_sepia"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/sepia"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.SepiaAction>
-    <com.android.gallery3d.photoeditor.actions.NegativeAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_negative"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/negative"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.NegativeAction>
-    <com.android.gallery3d.photoeditor.actions.TintAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_tint"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/tint"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.TintAction>
-    <com.android.gallery3d.photoeditor.actions.DuotoneAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_duotone"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/duotone"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.DuotoneAction>
-    <com.android.gallery3d.photoeditor.actions.DoodleAction style="@style/Effect"
-        android:tag="@string/doodle_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_doodle"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/doodle"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.DoodleAction>
-
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_exposure.xml b/res/layout/photoeditor_effects_exposure.xml
deleted file mode 100644
index b353489..0000000
--- a/res/layout/photoeditor_effects_exposure.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/EffectsContainer">
-
-    <com.android.gallery3d.photoeditor.actions.FillLightAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_filllight"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/filllight"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.FillLightAction>
-    <com.android.gallery3d.photoeditor.actions.HighlightAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_highlight"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/highlight"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.HighlightAction>
-    <com.android.gallery3d.photoeditor.actions.ShadowAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_shadow"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/shadow"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.ShadowAction>
-    <com.android.gallery3d.photoeditor.actions.AutoFixAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_autofix"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/autofix"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.AutoFixAction>
-
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_fix.xml b/res/layout/photoeditor_effects_fix.xml
deleted file mode 100644
index 924190b..0000000
--- a/res/layout/photoeditor_effects_fix.xml
+++ /dev/null
@@ -1,99 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/EffectsContainer">
-
-    <com.android.gallery3d.photoeditor.actions.CropAction style="@style/Effect"
-        android:tag="@string/crop_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_crop"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/crop"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.CropAction>
-    <com.android.gallery3d.photoeditor.actions.RedEyeAction style="@style/Effect"
-        android:tag="@string/redeye_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_redeye"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/redeye"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.RedEyeAction>
-    <com.android.gallery3d.photoeditor.actions.FaceliftAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_facelift"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/facelift"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.FaceliftAction>
-    <com.android.gallery3d.photoeditor.actions.FaceTanAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_facetan"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/facetan"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.FaceTanAction>
-    <com.android.gallery3d.photoeditor.actions.StraightenAction style="@style/Effect"
-        android:tag="@string/straighten_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_straighten"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/straighten"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.StraightenAction>
-    <com.android.gallery3d.photoeditor.actions.RotateAction style="@style/Effect"
-        android:tag="@string/rotate_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_rotate"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/rotate"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.RotateAction>
-    <com.android.gallery3d.photoeditor.actions.FlipAction style="@style/Effect"
-        android:tag="@string/flip_tooltip">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_flip"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/flip"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.FlipAction>
-    <com.android.gallery3d.photoeditor.actions.SharpenAction style="@style/Effect">
-        <ImageView
-            style="@style/EffectIcon"
-            android:src="@drawable/photoeditor_effect_sharpen"/>
-        <TextView
-            android:id="@+id/effect_label"
-            android:text="@string/sharpen"
-            style="@style/EffectLabel"/>
-    </com.android.gallery3d.photoeditor.actions.SharpenAction>
-
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_gallery.xml b/res/layout/photoeditor_effects_gallery.xml
deleted file mode 100644
index 16a2366..0000000
--- a/res/layout/photoeditor_effects_gallery.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<!-- The linear-layout is used to center content that cannot fill scroll-view -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/translucent_black"
-    android:gravity="center_horizontal">
-
-    <HorizontalScrollView
-        android:id="@+id/scroll_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:scrollbars="none"/>
-</LinearLayout>
diff --git a/res/layout/photoeditor_effects_menu.xml b/res/layout/photoeditor_effects_menu.xml
deleted file mode 100644
index 2cc1701..0000000
--- a/res/layout/photoeditor_effects_menu.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/ActionBarInner"
-    android:background="@drawable/photoeditor_actionbar_translucent_bottom">
-
-    <LinearLayout
-        android:id="@+id/toggles"
-        style="@style/EffectsMenuContainer">
-
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/exposure_button"
-            style="@style/EffectsMenuActionButton"
-            android:src="@drawable/photoeditor_exposure"
-            android:contentDescription="@string/photoeditor_exposure"/>
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/artistic_button"
-            style="@style/EffectsMenuActionButton"
-            android:src="@drawable/photoeditor_artistic"
-            android:contentDescription="@string/photoeditor_artistic"/>
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/color_button"
-            style="@style/EffectsMenuActionButton"
-            android:src="@drawable/photoeditor_color"
-            android:contentDescription="@string/photoeditor_color"/>
-        <com.android.gallery3d.photoeditor.ImageActionButton
-            android:id="@+id/fix_button"
-            style="@style/EffectsMenuActionButton"
-            android:src="@drawable/photoeditor_fix"
-            android:contentDescription="@string/photoeditor_fix"/>
-    </LinearLayout>
-
-</FrameLayout>
diff --git a/res/layout/photoeditor_flip_view.xml b/res/layout/photoeditor_flip_view.xml
deleted file mode 100644
index 150b24e..0000000
--- a/res/layout/photoeditor_flip_view.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.FlipView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_main.xml b/res/layout/photoeditor_main.xml
deleted file mode 100644
index a040ca7..0000000
--- a/res/layout/photoeditor_main.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.Toolbar
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/toolbar"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent">
-
-    <com.android.gallery3d.photoeditor.PhotoView
-        android:id="@+id/photo_view"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent"/>
-
-    <com.android.gallery3d.photoeditor.EffectsBar
-        android:id="@+id/effects_bar"
-        style="@style/EffectsBar">
-
-        <com.android.gallery3d.photoeditor.EffectsMenu
-            android:id="@+id/effects_menu"
-            style="@style/ActionBarOuter"/>
-
-    </com.android.gallery3d.photoeditor.EffectsBar>
-
-    <com.android.gallery3d.photoeditor.ActionBar
-        android:id="@+id/action_bar"
-        style="@style/ActionBarOuter"
-        android:layout_alignParentTop="true"/>
-
-</com.android.gallery3d.photoeditor.Toolbar>
diff --git a/res/layout/photoeditor_rotate_view.xml b/res/layout/photoeditor_rotate_view.xml
deleted file mode 100644
index 0e85fd4..0000000
--- a/res/layout/photoeditor_rotate_view.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.RotateView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photoeditor_scale_seekbar.xml b/res/layout/photoeditor_scale_seekbar.xml
deleted file mode 100644
index 15fc234..0000000
--- a/res/layout/photoeditor_scale_seekbar.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.ScaleSeekBar
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/SeekBar"/>
diff --git a/res/layout/photoeditor_touch_view.xml b/res/layout/photoeditor_touch_view.xml
deleted file mode 100644
index a6da078..0000000
--- a/res/layout/photoeditor_touch_view.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<com.android.gallery3d.photoeditor.actions.TouchView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/FullscreenToolView"/>
diff --git a/res/layout/photopage_bottom_controls.xml b/res/layout/photopage_bottom_controls.xml
index 8fe594d..f3226e6 100644
--- a/res/layout/photopage_bottom_controls.xml
+++ b/res/layout/photopage_bottom_controls.xml
@@ -10,7 +10,7 @@
         android:visibility="gone">
         <ImageButton
                 android:id="@+id/photopage_bottom_control_edit"
-                android:src="@drawable/ic_photoeditor_effects"
+                android:src="@drawable/ic_menu_edit_holo_dark"
                 android:background="@drawable/photopage_bottom_button_background"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
diff --git a/res/layout/rotate_dialog.xml b/res/layout/rotate_dialog.xml
new file mode 100644
index 0000000..c62ce91
--- /dev/null
+++ b/res/layout/rotate_dialog.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/rotate_dialog_root_layout"
+        android:clickable="true"
+        android:gravity="center"
+        android:visibility="gone"
+        android:background="#55000000"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <com.android.camera.ui.RotateLayout
+            android:id="@+id/rotate_dialog_layout"
+            android:gravity="center"
+            android:layout_gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" >
+
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_gravity="center"
+                android:background="@color/popup_background"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+
+            <LinearLayout android:id="@+id/rotate_dialog_title_layout"
+                    android:orientation="vertical"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+
+                <TextView android:id="@+id/rotate_dialog_title"
+                        style="@style/TextAppearance.DialogWindowTitle"
+                        android:gravity="center_vertical"
+                        android:layout_marginLeft="16dip"
+                        android:layout_marginRight="16dip"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:minHeight="64dp"/>
+                <View style="@style/PopupTitleSeparator" />
+            </LinearLayout>
+
+            <LinearLayout
+                    android:orientation="horizontal"
+                    android:background="@color/popup_background"
+                    android:padding="9dp"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+
+                <ProgressBar
+                        android:id="@+id/rotate_dialog_spinner"
+                        android:layout_gravity="center_vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+                <TextView
+                        style="@style/TextAppearance.Medium"
+                        android:id="@+id/rotate_dialog_text"
+                        android:layout_gravity="center_vertical"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content" />
+            </LinearLayout>
+
+            <ImageView android:background="@drawable/list_divider"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content" />
+
+            <LinearLayout android:id="@+id/rotate_dialog_button_layout"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:gravity="center"
+                    android:minHeight="48dp"
+                    android:orientation="horizontal">
+
+                <Button android:id="@+id/rotate_dialog_button2"
+                        style="@style/Widget.Button.Borderless"
+                        android:gravity="center"
+                        android:maxLines="2"
+                        android:minHeight="48dp"
+                        android:textSize="14sp"
+                        android:layout_weight="1"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content" />
+                <ImageView android:background="@drawable/list_divider"
+                        android:layout_width="wrap_content"
+                        android:layout_height="match_parent" />
+                <Button android:id="@+id/rotate_dialog_button1"
+                        style="@style/Widget.Button.Borderless"
+                        android:gravity="center"
+                        android:maxLines="2"
+                        android:minHeight="48dp"
+                        android:textSize="14sp"
+                        android:layout_weight="1"
+                        android:layout_width="0dp"
+                        android:layout_height="wrap_content" />
+            </LinearLayout>
+        </LinearLayout>
+    </com.android.camera.ui.RotateLayout>
+</FrameLayout>
diff --git a/res/layout/rotate_text_toast.xml b/res/layout/rotate_text_toast.xml
new file mode 100644
index 0000000..2c89b6f
--- /dev/null
+++ b/res/layout/rotate_text_toast.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<com.android.camera.ui.RotateLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/rotate_toast"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:visibility="gone">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="@drawable/toast_frame_holo">
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:textAppearanceMedium"
+            android:textColor="@android:color/white"
+            android:shadowColor="#BB000000"
+            android:shadowRadius="2.75" />
+    </FrameLayout>
+</com.android.camera.ui.RotateLayout>
+
+
diff --git a/res/layout/setting_item.xml b/res/layout/setting_item.xml
new file mode 100644
index 0000000..8571003
--- /dev/null
+++ b/res/layout/setting_item.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011, 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.
+-->
+<com.android.camera.ui.CheckedLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        tools:ignore="UseCompoundDrawables"
+        style="@style/SettingRow">
+    <TextView android:id="@+id/text"
+            style="@style/SettingItemTitle" />
+    <ImageView android:id="@+id/image"
+            android:layout_height="@dimen/setting_item_icon_width"
+            android:layout_width="@dimen/setting_item_icon_width"
+            android:scaleType="fitCenter"
+            android:adjustViewBounds="true" />
+</com.android.camera.ui.CheckedLinearLayout>
diff --git a/res/layout/time_interval_picker.xml b/res/layout/time_interval_picker.xml
new file mode 100644
index 0000000..d2a9462
--- /dev/null
+++ b/res/layout/time_interval_picker.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+
+<!-- Layout of time interval picker -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/time_interval_picker"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent">
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+        <TextView
+                android:id="@+id/set_time_interval_title"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:paddingTop="5dip"
+                android:gravity="center"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:text="@string/set_time_interval"/>
+    </LinearLayout>
+
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="16dip"
+            android:paddingRight="16dip" >
+
+        <!-- time interval duration -->
+        <NumberPicker
+                android:id="@+id/duration"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:focusable="false" />
+
+        <!-- time interval duration units (seconds/minutes/hours) -->
+        <NumberPicker
+                android:id="@+id/duration_unit"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="2"
+                android:layout_marginLeft="20dip"
+                android:focusable="false" />
+
+    </LinearLayout>
+</LinearLayout>
+
diff --git a/res/layout/time_interval_popup.xml b/res/layout/time_interval_popup.xml
new file mode 100644
index 0000000..9cf224a
--- /dev/null
+++ b/res/layout/time_interval_popup.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011, 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.
+-->
+
+<com.android.camera.ui.TimeIntervalPopup xmlns:android="http://schemas.android.com/apk/res/android"
+        style="@style/SettingPopupWindow">
+
+    <LinearLayout android:orientation="vertical"
+            android:background="@color/popup_background"
+            android:layout_height="wrap_content"
+            android:layout_width="@dimen/big_setting_popup_window_width">
+
+        <LinearLayout android:orientation="horizontal"
+                android:layout_height="wrap_content"
+                android:layout_width="match_parent">
+            <TextView android:id="@+id/title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:gravity="center_vertical"
+                    android:ellipsize="end"
+                    android:layout_weight="1"
+                    android:minHeight="@dimen/popup_title_frame_min_height"
+                    style="@style/PopupTitleText" />
+            <Switch
+                    android:id="@+id/time_lapse_switch"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:layout_weight="0"
+                    android:layout_marginRight="8dp"
+                    android:layout_gravity="right|center_vertical" />
+        </LinearLayout>
+
+        <View style="@style/PopupTitleSeparator" />
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+            <TextView
+                    android:id="@+id/set_time_interval_help_text"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingTop="16dip"
+                    android:paddingLeft="16dip"
+                    android:paddingRight="16dip"
+                    android:paddingBottom="16dip"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    android:text="@string/set_time_interval_help"/>
+        </LinearLayout>
+
+        <LinearLayout android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal" >
+                <include layout="@layout/time_interval_picker"/>
+        </LinearLayout>
+
+        <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                android:divider="?android:attr/dividerHorizontal"
+                android:showDividers="beginning"
+                android:dividerPadding="0dip">
+            <Button android:id="@+id/time_lapse_interval_set_button"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_horizontal"
+                    android:textAppearance="?android:attr/textAppearanceMedium"
+                    style="?android:attr/buttonBarButtonStyle"
+                    android:text="@string/time_lapse_interval_set" />
+        </LinearLayout>
+    </LinearLayout>
+
+</com.android.camera.ui.TimeIntervalPopup>
diff --git a/res/layout/trim_menu.xml b/res/layout/trim_menu.xml
index b619220..e233392 100644
--- a/res/layout/trim_menu.xml
+++ b/res/layout/trim_menu.xml
@@ -27,6 +27,6 @@
         android:textAllCaps="true"
         android:textSize="14sp"
         android:gravity="left|center_vertical"
-        android:drawableLeft="@drawable/ic_menu_savephoto"
+        android:drawableLeft="@drawable/menu_save_photo"
         android:drawablePadding="8dp" />
 </FrameLayout>
diff --git a/res/layout/video_module.xml b/res/layout/video_module.xml
new file mode 100644
index 0000000..790f3eb
--- /dev/null
+++ b/res/layout/video_module.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<!-- This layout is shared by phone and tablet in landscape orientation. -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_app_root"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+    <com.android.camera.PreviewFrameLayout android:id="@+id/frame"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:layout_gravity="center">
+        <com.android.camera.ui.PreviewSurfaceView
+                android:id="@+id/preview_surface_view"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="gone"/>
+        <FrameLayout android:id="@+id/preview_border"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:visibility="gone"
+                android:background="@drawable/ic_snapshot_border" />
+        <com.android.camera.ui.RenderOverlay
+            android:id="@+id/render_overlay"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+        <com.android.camera.ui.RotateLayout android:id="@+id/recording_time_rect"
+                style="@style/ViewfinderLabelLayout">
+            <include layout="@layout/viewfinder_labels_video" android:id="@+id/labels" />
+        </com.android.camera.ui.RotateLayout>
+        <ImageView android:id="@+id/review_image"
+                android:layout_height="match_parent"
+                android:layout_width="match_parent"
+                android:visibility="gone"
+                android:background="@android:color/black"/>
+        <ImageView
+                android:id="@+id/btn_play"
+                style="@style/ReviewControlIcon"
+                android:layout_centerInParent="true"
+                android:src="@drawable/ic_gallery_play_big"
+                android:visibility="gone"
+                android:onClick="onReviewPlayClicked"/>
+    </com.android.camera.PreviewFrameLayout>
+
+</merge>
diff --git a/res/layout/viewfinder_labels_video.xml b/res/layout/viewfinder_labels_video.xml
new file mode 100644
index 0000000..cfe3b02
--- /dev/null
+++ b/res/layout/viewfinder_labels_video.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<!-- This layout is shared by phone and tablet in portrait or landscape orientation. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent">
+    <TextView android:id="@+id/recording_time"
+            style="@style/OnViewfinderLabel"
+            android:gravity="center"
+            android:drawableLeft="@drawable/ic_recording_indicator"
+            android:drawablePadding="5dp"
+            android:visibility="gone" />
+    <TextView android:id="@+id/time_lapse_label"
+            android:text="@string/time_lapse_title"
+            style="@style/OnViewfinderLabel"
+            android:visibility="gone" />
+</LinearLayout>
diff --git a/res/menu/filtershow_activity_menu.xml b/res/menu/filtershow_activity_menu.xml
index efb8747..0a8f4e9 100644
--- a/res/menu/filtershow_activity_menu.xml
+++ b/res/menu/filtershow_activity_menu.xml
@@ -2,31 +2,26 @@
     <item
         android:id="@+id/menu_share"
         android:actionProviderClass="android.widget.ShareActionProvider"
-        android:showAsAction="always"
+        android:showAsAction="never"
         android:enabled="false"
+        android:visible="false"
         android:title="@string/share"/>
     <item
         android:id="@+id/undoButton"
         android:icon="@drawable/filtershow_button_undo"
-        android:showAsAction="never"
+        android:showAsAction="always"
         android:title="@string/filtershow_undo"/>
     <item
         android:id="@+id/redoButton"
         android:icon="@drawable/filtershow_button_redo"
-        android:showAsAction="never"
+        android:showAsAction="always"
         android:title="@string/filtershow_redo"/>
     <item
         android:id="@+id/resetHistoryButton"
         android:title="@string/reset"/>
     <item
-        android:id="@+id/operationsButton"
-        android:icon="@drawable/filtershow_button_operations"
-        android:showAsAction="never"
-        android:visible="true"
-        android:title="@string/show_history_panel"/>
-    <item
         android:id="@+id/showImageStateButton"
         android:showAsAction="never"
-        android:visible="false"
+        android:visible="true"
         android:title="@string/show_imagestate_panel" />
 </menu>
diff --git a/res/menu/filtershow_menu_draw.xml b/res/menu/filtershow_menu_draw.xml
new file mode 100644
index 0000000..2960c1f
--- /dev/null
+++ b/res/menu/filtershow_menu_draw.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <group android:id="@+id/curves_popupmenu" >
+        <item
+            android:id="@+id/draw_menu_style_line"
+            android:title="@string/draw_style_line" />
+         <item
+            android:id="@+id/draw_menu_style_brush_marker"
+            android:title="@string/draw_style_brush_marker"/>
+         <item
+            android:id="@+id/draw_menu_style_brush_spatter"
+            android:title="@string/draw_style_brush_spatter"/>
+         <item
+            android:id="@+id/draw_menu_size"
+            android:title="@string/draw_size" />
+        <item
+            android:id="@+id/draw_menu_color"
+            android:title="@string/draw_color"/>
+        <item
+            android:id="@+id/draw_menu_clear"
+            android:title="@string/draw_clear"/>
+    </group>
+
+</menu>
\ No newline at end of file
diff --git a/res/menu/gallery.xml b/res/menu/gallery.xml
new file mode 100644
index 0000000..dc36787
--- /dev/null
+++ b/res/menu/gallery.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/menu_camera"
+        android:icon="@android:drawable/ic_menu_camera"
+        android:showAsAction="ifRoom"
+        android:title="@string/menu_camera"/>
+    <item
+        android:id="@+id/menu_search"
+        android:icon="@android:drawable/ic_menu_search"
+        android:showAsAction="ifRoom"
+        android:title="@string/menu_search"/>
+    <item
+        android:id="@+id/menu_settings"
+        android:icon="@android:drawable/ic_menu_preferences"
+        android:showAsAction="never"
+        android:title="@string/settings"/>
+    <item
+        android:id="@+id/menu_help"
+        android:icon="@android:drawable/ic_menu_help"
+        android:showAsAction="never"
+        android:title="@string/help"/>
+</menu>
\ No newline at end of file
diff --git a/res/menu/gallery_multiselect.xml b/res/menu/gallery_multiselect.xml
new file mode 100644
index 0000000..d9365c1
--- /dev/null
+++ b/res/menu/gallery_multiselect.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@+id/menu_edit"
+            android:title="@string/edit"
+            android:visible="false"
+            android:showAsAction="ifRoom" />
+    <item android:id="@+id/menu_delete"
+            android:icon="@android:drawable/ic_menu_delete"
+            android:title="@string/delete"
+            android:visible="false"
+            android:showAsAction="ifRoom" />
+    <item android:id="@+id/menu_share"
+          android:title="@string/share"
+          android:showAsAction="ifRoom"
+          android:visible="false"
+          android:actionProviderClass="android.widget.ShareActionProvider" />
+    <item android:id="@+id/menu_crop"
+            android:title="@string/crop_action"
+            android:visible="false"
+            android:showAsAction="never" />
+    <item android:id="@+id/menu_trim"
+            android:title="@string/trim_action"
+            android:visible="false"
+            android:showAsAction="never" />
+    <item android:id="@+id/menu_mute"
+            android:title="@string/mute_action"
+            android:visible="false"
+            android:showAsAction="never" />
+    <item android:id="@+id/menu_set_as"
+            android:title="@string/set_as"
+            android:visible="false"
+            android:showAsAction="never" />
+</menu>
\ No newline at end of file
diff --git a/res/menu/ingest_menu_item_list_selection.xml b/res/menu/ingest_menu_item_list_selection.xml
new file mode 100644
index 0000000..2f020b6
--- /dev/null
+++ b/res/menu/ingest_menu_item_list_selection.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:id="@+id/ingest_switch_view"
+          android:showAsAction="always" />
+    <item android:id="@+id/import_items"
+          android:showAsAction="always|withText"
+          android:title="@string/Import" />
+</menu>
\ No newline at end of file
diff --git a/res/menu/photo.xml b/res/menu/photo.xml
index 569272e..48742d1 100644
--- a/res/menu/photo.xml
+++ b/res/menu/photo.xml
@@ -47,6 +47,10 @@
             android:title="@string/edit"
             android:showAsAction="never"
             android:visible="false" />
+    <item android:id="@+id/action_simple_edit"
+          android:title="@string/simple_edit"
+          android:showAsAction="never"
+          android:visible="false" />
     <item android:id="@+id/action_rotate_ccw"
             android:showAsAction="never"
             android:title="@string/rotate_left" />
@@ -59,6 +63,9 @@
     <item android:id="@+id/action_trim"
             android:title="@string/trim_action"
             android:showAsAction="never" />
+    <item android:id="@+id/action_mute"
+            android:title="@string/mute_action"
+            android:showAsAction="never" />
     <item android:id="@+id/action_setas"
             android:title="@string/set_image"
             android:showAsAction="never" />
diff --git a/res/mipmap-hdpi/ic_launcher_camera.png b/res/mipmap-hdpi/ic_launcher_camera.png
new file mode 100644
index 0000000..7b9d090
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_camera.png
Binary files differ
diff --git a/res/mipmap-hdpi/ic_launcher_video_camera.png b/res/mipmap-hdpi/ic_launcher_video_camera.png
new file mode 100644
index 0000000..d242657
--- /dev/null
+++ b/res/mipmap-hdpi/ic_launcher_video_camera.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_camera.png b/res/mipmap-mdpi/ic_launcher_camera.png
new file mode 100644
index 0000000..9d24f4e
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_camera.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_video_camera.png b/res/mipmap-mdpi/ic_launcher_video_camera.png
new file mode 100644
index 0000000..19f0e64
--- /dev/null
+++ b/res/mipmap-mdpi/ic_launcher_video_camera.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_camera.png b/res/mipmap-xhdpi/ic_launcher_camera.png
new file mode 100644
index 0000000..824161a
--- /dev/null
+++ b/res/mipmap-xhdpi/ic_launcher_camera.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_camera.png b/res/mipmap-xxhdpi/ic_launcher_camera.png
new file mode 100644
index 0000000..1e09a6b
--- /dev/null
+++ b/res/mipmap-xxhdpi/ic_launcher_camera.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_gallery.png b/res/mipmap-xxhdpi/ic_launcher_gallery.png
deleted file mode 100644
index 96cc1d1..0000000
--- a/res/mipmap-xxhdpi/ic_launcher_gallery.png
+++ /dev/null
Binary files differ
diff --git a/res/raw/backdropper.graph b/res/raw/backdropper.graph
new file mode 100644
index 0000000..1dff2df
--- /dev/null
+++ b/res/raw/backdropper.graph
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2011 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.
+//
+
+// Imports ---------------------------------------------------
+@import android.filterpacks.base;
+@import android.filterpacks.ui;
+@import android.filterpacks.videosrc;
+@import android.filterpacks.videoproc;
+@import android.filterpacks.videosink;
+
+@setting autoBranch = "synced";
+
+// Externals -------------------------------------------------
+
+@external textureSourceCallback;
+@external recordingWidth;
+@external recordingHeight;
+@external recordingProfile;
+@external recordingDoneListener;
+
+@external previewSurfaceTexture;
+@external previewWidth;
+@external previewHeight;
+
+@external orientation;
+
+@external learningDoneListener;
+
+// Filters ---------------------------------------------------
+
+// Camera input
+@filter SurfaceTextureSource source {
+  sourceListener = $textureSourceCallback;
+  width = $recordingWidth;
+  height = $recordingHeight;
+  closeOnTimeout = true;
+}
+
+// Background video input
+@filter MediaSource background {
+  sourceUrl = "no_file_specified";
+  waitForNewFrame = false;
+  sourceIsUrl = true;
+  orientation = $orientation;
+}
+
+// Background replacer
+@filter BackDropperFilter replacer {
+  autowbToggle = 1;
+  learningDoneListener = $learningDoneListener;
+  orientation = $orientation;
+}
+
+// Display output
+@filter SurfaceTextureTarget display {
+  surfaceTexture = $previewSurfaceTexture;
+  width = $previewWidth;
+  height = $previewHeight;
+}
+
+// Recording output
+@filter MediaEncoderFilter recorder {
+  recordingProfile = $recordingProfile;
+  recordingDoneListener = $recordingDoneListener;
+  recording = false;
+  width = $recordingWidth;
+  height = $recordingHeight;
+  // outputFile, orientationHint, inputRegion,
+  // audioSource, listeners, captureRate
+  // will be set when recording starts
+}
+
+// Connections -----------------------------------------------
+@connect source[video] => replacer[video];
+@connect background[video] => replacer[background];
+@connect replacer[video] => display[frame];
+@connect replacer[video] => recorder[videoframe];
+
diff --git a/res/raw/beep_once.ogg b/res/raw/beep_once.ogg
new file mode 100644
index 0000000..06e8be8
--- /dev/null
+++ b/res/raw/beep_once.ogg
Binary files differ
diff --git a/res/raw/beep_twice.ogg b/res/raw/beep_twice.ogg
new file mode 100644
index 0000000..94a7c14
--- /dev/null
+++ b/res/raw/beep_twice.ogg
Binary files differ
diff --git a/res/raw/blank.jpg b/res/raw/blank.jpg
new file mode 100644
index 0000000..509b5ad
--- /dev/null
+++ b/res/raw/blank.jpg
Binary files differ
diff --git a/res/raw/focus_complete.ogg b/res/raw/focus_complete.ogg
new file mode 100644
index 0000000..0db2683
--- /dev/null
+++ b/res/raw/focus_complete.ogg
Binary files differ
diff --git a/res/raw/goofy_face.graph b/res/raw/goofy_face.graph
new file mode 100644
index 0000000..90e0f3a
--- /dev/null
+++ b/res/raw/goofy_face.graph
@@ -0,0 +1,123 @@
+//
+// Copyright (C) 2011 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.
+//
+
+// Imports ---------------------------------------------------
+@import android.filterpacks.videosrc;
+@import android.filterpacks.videosink;
+@import android.filterpacks.ui;
+@import android.filterpacks.base;
+@import android.filterpacks.imageproc;
+
+@import com.google.android.filterpacks.facedetect;
+
+@setting autoBranch = "synced";
+
+// Externals -------------------------------------------------
+
+@external textureSourceCallback;
+@external recordingWidth;
+@external recordingHeight;
+@external recordingProfile;
+@external recordingDoneListener;
+
+@external previewSurfaceTexture;
+@external previewWidth;
+@external previewHeight;
+
+// Not used by this graph, but simplifies higher-level
+// graph initialization code.
+@external orientation;
+
+// Filters ---------------------------------------------------
+
+// Camera input
+@filter SurfaceTextureSource source {
+  sourceListener = $textureSourceCallback;
+  width = $recordingWidth;
+  height = $recordingHeight;
+  closeOnTimeout = true;
+}
+
+// Face detection
+@filter ToPackedGrayFilter toPackedGray {
+  owidth = 320;
+  oheight = 240;
+  keepAspectRatio = true;
+}
+
+@filter MultiFaceTrackerFilter faceTracker {
+  numChannelsDetector = 3;
+  quality = 0.0f;
+  smoothness = 0.2f;
+  minEyeDist = 25.0f;
+  rollRange = 45.0f;
+  numSkipFrames = 9;
+  trackingError = 1.0;
+  mouthOnlySmoothing = 0;
+  useAffineCorrection = 1;
+  patchSize = 15;
+}
+
+// Goofyface
+@filter GoofyFastRenderFilter goofyrenderer {
+  distortionAmount = 1.0;
+}
+
+// Display output
+@filter SurfaceTextureTarget display {
+  surfaceTexture = $previewSurfaceTexture;
+  width = $previewWidth;
+  height = $previewHeight;
+  renderMode = "stretch";
+}
+
+// Orientation rotation filter
+@filter FixedRotationFilter rotate {
+    rotation = 0;
+}
+
+// Orientation rotation filter for facemeta data
+@filter FaceMetaFixedRotationFilter metarotate {
+    rotation = 0;
+}
+
+
+// Recording output
+@filter MediaEncoderFilter recorder {
+  recordingProfile = $recordingProfile;
+  recordingDoneListener = $recordingDoneListener;
+  recording = false;
+  width = $recordingWidth;
+  height = $recordingHeight;
+  // outputFile, orientationHint, inputRegion,
+  // audioSource, listeners, captureRate
+  // will be set when recording starts
+}
+
+// Connections -----------------------------------------------
+// camera -> faceTracker
+@connect source[video] => rotate[image];
+@connect rotate[image] => toPackedGray[image];
+@connect toPackedGray[image] => faceTracker[image];
+// camera -> goofy
+@connect source[video] => goofyrenderer[image];
+// faceTracker -> metarotate -> goofy
+@connect faceTracker[faces] => metarotate[faces];
+@connect metarotate[faces] => goofyrenderer[faces];
+// goofy -> display out
+@connect goofyrenderer[outimage] => display[frame];
+// goofy -> record
+@connect goofyrenderer[outimage] => recorder[videoframe];
diff --git a/res/raw/video_record.ogg b/res/raw/video_record.ogg
new file mode 100644
index 0000000..d2dee03
--- /dev/null
+++ b/res/raw/video_record.ogg
Binary files differ
diff --git a/res/values-af/filtershow_strings.xml b/res/values-af/filtershow_strings.xml
index 83ea198..2092e10 100644
--- a/res/values-af/filtershow_strings.xml
+++ b/res/values-af/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Kan die beeld nie laai nie!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Stel muurpapier"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Kon nie foto aflaai nie. Netwerk nie beskikbaar nie."</string>
     <string name="original" msgid="3524493791230430897">"Oorspronklike"</string>
     <string name="borders" msgid="2067345080568684614">"Grense"</string>
-    <string name="done" msgid="3112344807927554662">"Klaar"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Ontdoen"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Herdoen"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Wys geskiedenis"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Versteek geskiedenis"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Wys prenttoestand"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Versteek prenttoestand"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Wys toegepaste effekte"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Versteek toegepaste effekte"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Instellings"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Daar is ongestoorde veranderinge aan hierdie prent."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Wil jy stoor voor jy uitgaan?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Stoor en gaan uit"</string>
+    <string name="exit" msgid="242642957038770113">"Gaan uit"</string>
     <string name="history" msgid="455767361472692409">"Geskiedenis"</string>
     <string name="reset" msgid="9013181350779592937">"Stel terug"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Huidige prenttoestand"</string>
+    <string name="imageState" msgid="8632586742752891968">"Toegepaste uitwerkings"</string>
     <string name="compare_original" msgid="8140838959007796977">"Vergelyk"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Pas toe"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Stel terug"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Geen"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Vasgestel"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Klein planeet"</string>
     <string name="exposure" msgid="6526397045949374905">"Beligting"</string>
     <string name="sharpness" msgid="6463103068318055412">"Skerpheid"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Outokleur"</string>
     <string name="hue" msgid="6231252147971086030">"Kleur"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skaduwees"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Ligstrepe"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurwes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjet"</string>
     <string name="redeye" msgid="4508883127049472069">"Rooi oog"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Skets"</string>
     <string name="straighten" msgid="26025591664983528">"Maak reguit"</string>
     <string name="crop" msgid="5781263790107850771">"Snoei"</string>
     <string name="rotate" msgid="2796802553793795371">"Draai"</string>
     <string name="mirror" msgid="5482518108154883096">"Spieël"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatief"</string>
     <string name="none" msgid="6633966646410296520">"Geen"</string>
+    <string name="edge" msgid="7036064886242147551">"Kante"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Verklein prent"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rooi"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Groen"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blou"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Styl"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Grootte"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Kleur"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Lyne"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Merker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spatsels"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Vee uit"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Kies gepasmaakte kleur"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Kies kleur"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Kies grootte"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Oorspronklike"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultaat"</string>
 </resources>
diff --git a/res/values-af/photoeditor_strings.xml b/res/values-af/photoeditor_strings.xml
deleted file mode 100644
index f276a8b..0000000
--- a/res/values-af/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotoateljee"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Kon nie die foto laai nie"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Kon nie geredigeerde foto stoor nie"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Geredigeerde foto gestoor in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Raak ontslae van ongestoorde veranderinge?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"Stoor"</string>
-    <string name="autofix" msgid="1663414996270538748">"Outokorrigeer"</string>
-    <string name="crop" msgid="7598378507763334041">"Snoei"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Kruisproses"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentêr"</string>
-    <string name="doodle" msgid="1686409894518940990">"Krabbel"</string>
-    <string name="duotone" msgid="8145893940788467106">"Twee kleure"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Gesigsverbruining"</string>
-    <string name="filllight" msgid="2644989991700022526">"Vullig"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Visoog"</string>
-    <string name="flip" msgid="2357692401826287480">"Keer om"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmgrein"</string>
-    <string name="grayscale" msgid="7641563843060945228">"S &amp; W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Swart en wit"</string>
-    <string name="highlight" msgid="3902653944386623972">"Helder lig"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatief"</string>
-    <string name="posterize" msgid="4139212359561383385">"Plakkateer"</string>
-    <string name="redeye" msgid="4958448806369928239">"Rooi oog"</string>
-    <string name="rotate" msgid="6607597269792373083">"Draai"</string>
-    <string name="saturation" msgid="8621322012271169931">"Kleurdiepte"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Skadu\'s"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Maak skerper"</string>
-    <string name="straighten" msgid="5217801513491493491">"Maak reguit"</string>
-    <string name="temperature" msgid="1607987938521534517">"Warmte"</string>
-    <string name="tint" msgid="154435943863418434">"Tint"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjet"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Trek merkers om foto te sny"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Teken op foto om te krabbel"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Trek foto om dit om te keer"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Tik op rooi oë om hulle te verwyder"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Trek foto om dit te draai"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Trek foto om dit reguit te trek"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Beligtingeffekte"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Kleureffekte"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Artistieke effekte"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Maak reg"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Ontdoen"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Herdoen"</string>
-</resources>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 9cb51e0..4a9d63c 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Draai na regs"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Kon nie die item vind nie."</string>
     <string name="edit" msgid="1502273844748580847">"Redigeer"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Eenvoudige redigeer"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Verwerk kasversoeke"</string>
     <string name="caching_label" msgid="4521059045896269095">"Kas tans..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Snoei"</string>
     <string name="trim_action" msgid="703098114452883524">"Snoei"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Demp"</string>
     <string name="set_as" msgid="3636764710790507868">"Stel as"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Kan nie video demp nie."</string>
     <string name="video_err" msgid="7003051631792271009">"Kan nie video speel nie."</string>
     <string name="group_by_location" msgid="316641628989023253">"Volgens ligging"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Volgens tyd"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Outo"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flits gevuur"</string>
     <string name="flash_off" msgid="1445443413822680010">"Geen flits"</string>
+    <string name="unknown" msgid="3506693015896912952">"Onbekend"</string>
     <string name="ffx_original" msgid="372686331501281474">"Oorspronklike"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Kits"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Geen eksterne berging beskikbaar nie"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstrook-aansig"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Roosteraansig"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Volskerm-aansig"</string>
     <string name="trimming" msgid="9122385768369143997">"Snoei tans"</string>
+    <string name="muting" msgid="5094925919589915324">"Demp tans"</string>
     <string name="please_wait" msgid="7296066089146487366">"Wag asseblief"</string>
-    <string name="save_into" msgid="4960537214388766062">"Stoor tans gesnoeide video in album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Stoor video na <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Kan nie snoei nie: teikenvideo is te kort"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Weergewing van panorama"</string>
     <string name="save" msgid="613976532235060516">"Stoor"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skandeer tans inhoud..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d items geskandeer"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item geskandeer"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d items geskandeer"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorteer tans..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Klaar geskandeer"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Voer tans in..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Daar is geen inhoud beskikbaar om op hierdie toestel in te voer nie."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Daar is geen MTP-toestel gekoppel nie"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerafout"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Kan nie aan die kamera koppel nie."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera is gedeaktiveer weens sekuriteitsbeleide."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Wag asseblief…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Heg USB-berging voordat kamera gebruik word."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Sit \'n SD-kaart in voor jy die kamera gebruik."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Berei tans USB-berging voor…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Berei tans SD-kaart voor..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Kon nie toegang tot USB-berging kry nie."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Kon nie toegang tot SD-kaart kry."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"KANSELLEER"</string>
+    <string name="review_ok" msgid="1156261588693116433">"KLAAR"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Tydsverloop-opname"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Kies kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Terug"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Voorkant"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Stoor ligging"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LIGGING"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Aftel-tydhouer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekonde"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sekondes"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Biep tydens aftelling"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Af"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Aan"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videogehalte"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Hoog"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Laag"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Tydsverloop"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kamera-instellings"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokamera-instellings"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Prentgrootte"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M pixels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M pixels"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 M pieksels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 M pieksels (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M pixels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M pixels"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Outo"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Oneindig"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"OUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"ONEINDIG"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flits-modus"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"FLITS-MODUS"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Outo"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Aan"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Af"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"OUTO-FLITS"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLITS AAN"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLITS AF"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Witbalans"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"WITBALANS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Outo"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Gloeiend"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Daglig"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluoresserend"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Bewolk"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"OUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"GLOEIEND"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAGLIG"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESSEREND"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"BEWOLK"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Toneel-modus"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Outo"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Handeling"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nag"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Sonsondergang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Partytjie"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"GEEN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"HANDELING"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NAG"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SONSONDERGANG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PARTYTJIE"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"AFTEL-TYDHOUER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TYDHOUER AF"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKONDE"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKONDES"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKONDES"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKONDES"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Kan nie in toneelmodus gekies word nie."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Beligting"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"BELIGTING"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"VOORSTE KAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"AGTERSTE KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Jou USB-berging se stoorspasie raak min. Verander die gehalte-instelling of vee \'n paar prente of ander lêers uit."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Jou SD-kaart raak vol. Verander die gehalte-instelling of vee \'n paar prente of ander lêers uit."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Groottebeperking bereik."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Te vinnig"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Berei panorama voor"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Kon nie panorama stoor nie."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Lê panorama vas"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Wag vir vorige panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Stoor tans…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Weergewing van panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Raak om te fokus."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effekte"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Geen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Druk"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Groot oë"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Groot mond"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Klein mond"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Groot neus"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Klein oë"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"In die ruimte"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Sonsondergang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Jou video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Sit jou toestel neer."\n"Stap vir \'n oomblik buite sig."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Raak om foto tydens opname te neem."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video-opname het begin."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video-opname het gestop."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Video-momentopname is gedeaktiveer wanneer spesiale effekte aan is."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Vee effekte uit"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"LAWWE GESIGTE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"AGTERGROND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Sluiterknoppie"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Kieslysknoppie"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Mees onlangse foto"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Skakelaar vir voorste of agterste kamera"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kamera-, video- of panoramakieser"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Meer instellingkontroles"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Maak instellingkontroles toe"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoembeheer"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Verminder %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Vermeerder %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s merkblokkie"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Skakel oor na foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Skakel oor na video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Skakel oor na panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Skakel oor na nuwe panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Kanselleer hersiening"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Voltooi hersiening"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Hersien neem weer"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Speel video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Laat video wag"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Herlaai video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Video-speler se tydbalk"</string>
+    <string name="capital_on" msgid="5491353494964003567">"AAN"</string>
+    <string name="capital_off" msgid="7231052688467970897">"AF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Af"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekonde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ure"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekondes"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minute"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ure"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Klaar"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Stel tydinterval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Tydsverloop-kenmerk is af. Skakel dit aan om tydstussenpose te stel."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Aftel-tydhouer is af. Skakel dit aan om af te tel voor jy \'n foto neem."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Stel tydsduur in sekondes"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Tel af om \'n foto te neem"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Moet foto-liggings onthou word?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Merk jou foto\'s en video\'s met die liggings waar hulle geneem is."\n\n"Ander programme kan toegang kry tot hierdie inligting saam met jou gestoorde prente."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nee dankie"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Soek"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foto\'s"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"NOG OPSIES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"INSTELLINGS"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foto\'s"</item>
+  </plurals>
 </resources>
diff --git a/res/values-am/filtershow_strings.xml b/res/values-am/filtershow_strings.xml
index ed13a2f..04fb893 100644
--- a/res/values-am/filtershow_strings.xml
+++ b/res/values-am/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"ምስሉን መጫን አልተቻለም!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"ልጥፍ በማዘጋጀት ላይ"</string>
+    <string name="download_failure" msgid="5923323939788582895">"ፎቶን ማውረድ አልተቻለም። አውታረ መረብ አይገኝም።"</string>
     <string name="original" msgid="3524493791230430897">"የመጀመሪያው"</string>
     <string name="borders" msgid="2067345080568684614">"ድንበሮች"</string>
-    <string name="done" msgid="3112344807927554662">"ተከናውኗል"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"ቀልብስ"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"ድገም"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"ታሪክ አሳይ"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"ታሪክ ደብቅ"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"የምስል ሁኔታን አሳይ"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"የምስል ሁኔታን ደብቅ"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"የተተገበሩ ተጽዕኖዎችን አሳይ"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"የተተገበሩ ተጽዕኖዎችን ደብቅ"</string>
     <string name="menu_settings" msgid="6428291655769260831">"ቅንብሮች"</string>
+    <string name="unsaved" msgid="8704442449002374375">"በዚህ ምስል ላይ"</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"ከመውጣትዎ በፊት ማስቀመጥ ይፈልጋሉ?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"አስቀምጥና ውጣ"</string>
+    <string name="exit" msgid="242642957038770113">"ውጣ"</string>
     <string name="history" msgid="455767361472692409">"ታሪክ"</string>
     <string name="reset" msgid="9013181350779592937">"ዳግም አስጀምር"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"የአሁኑ ምስል ሁኔታ"</string>
+    <string name="imageState" msgid="8632586742752891968">"የተተገበሩ ተጽዕኖዎች"</string>
     <string name="compare_original" msgid="8140838959007796977">"አወዳድር"</string>
     <string name="apply_effect" msgid="1218288221200568947">"ተግብር"</string>
     <string name="reset_effect" msgid="7712605581024929564">"ዳግም አስጀምር"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"ምንም"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"ቋሚ"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"ተጋላጭነት"</string>
     <string name="sharpness" msgid="6463103068318055412">"ሹልነት"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"ራስ-ቀለም መሙላት"</string>
     <string name="hue" msgid="6231252147971086030">"የቀለም ድባብ"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"ጥላዎች"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"ድምቀቶች"</string>
     <string name="curvesRGB" msgid="915010781090477550">"ጥምዞች"</string>
     <string name="vignette" msgid="934721068851885390">"ቪኜት"</string>
     <string name="redeye" msgid="4508883127049472069">"ቀይ አይን"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"መሳል"</string>
     <string name="straighten" msgid="26025591664983528">"ቀጥ አርግ"</string>
     <string name="crop" msgid="5781263790107850771">"ከርክም"</string>
     <string name="rotate" msgid="2796802553793795371">"አሽከርክር"</string>
     <string name="mirror" msgid="5482518108154883096">"መስታወት"</string>
+    <string name="negative" msgid="6998313764388022201">"ኔጌቲቭ"</string>
     <string name="none" msgid="6633966646410296520">"ምንም"</string>
+    <string name="edge" msgid="7036064886242147551">"ጠርዞች"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"ናሙና ማውረድ"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"ቀ አ ሰ"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"ቀይ"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"አረንጓዴ"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"ሰማያዊ"</string>
+    <string name="draw_style" msgid="2036125061987325389">"ቅጥ"</string>
+    <string name="draw_size" msgid="4360005386104151209">"መጠን"</string>
+    <string name="draw_color" msgid="2119030386987211193">"ቀለም"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"መስመሮች"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"አመልካች"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"ነጠብጣብ"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"አጽዳ"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"ብጁ ቀለም ይምረጡ"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"ቀለም ይምረጡ"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"መጠን ይምረጡ"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"እሺ"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"የመጀመሪያው"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"ውጤት"</string>
 </resources>
diff --git a/res/values-am/photoeditor_strings.xml b/res/values-am/photoeditor_strings.xml
deleted file mode 100644
index 8ab71f3..0000000
--- a/res/values-am/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"ፎቶውን መጫን አልተቻለም"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"አርትዖት የተደረገበት ፎቶ ማስቀመጥ አልተቻለም"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"አርትዖት የተደረገበት ፎቶ <xliff:g id="FOLDER_NAME">%s</xliff:g> ላይ ተቀምጧል"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"ያልተቀመጡ ለውጦች ይወገዱ?"</string>
-    <string name="yes" msgid="5402582493291792293">"አዎ"</string>
-    <string name="save" msgid="5516670392524294967">"አስቀምጥ"</string>
-    <string name="autofix" msgid="1663414996270538748">"በራስ-ጠግን"</string>
-    <string name="crop" msgid="7598378507763334041">"ክፈፍ"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"ክሮስ ፕሮሰስ"</string>
-    <string name="documentary" msgid="50396326708699797">"ዘጋቢ ፊልም"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
-    <string name="facelift" msgid="6205748523156172637">"ፊት አድምቅ"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"ብርሃን ሙላ"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"በፍጥነት ግለጥ"</string>
-    <string name="grain" msgid="7487585304579789098">"የፊልም ግሬይን"</string>
-    <string name="grayscale" msgid="7641563843060945228">"B&amp;W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"ጥቁር እና ነጭ"</string>
-    <string name="highlight" msgid="3902653944386623972">"የተመረጡ"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"ኔጌቲቭ"</string>
-    <string name="posterize" msgid="4139212359561383385">"ግራጫማ ፎቶ"</string>
-    <string name="redeye" msgid="4958448806369928239">"ቀይነጥብ"</string>
-    <string name="rotate" msgid="6607597269792373083">"አሽከርክር"</string>
-    <string name="saturation" msgid="8621322012271169931">"ደማቅ"</string>
-    <string name="sepia" msgid="7978093531824705601">"ፈዘዝ ያለ ቡኒ"</string>
-    <string name="shadow" msgid="8235188588101973090">"ጥላዎች"</string>
-    <string name="sharpen" msgid="8449662378104403230">"አሹል"</string>
-    <string name="straighten" msgid="5217801513491493491">"ቀጥ አርግ"</string>
-    <string name="temperature" msgid="1607987938521534517">"አሟሙቅ"</string>
-    <string name="tint" msgid="154435943863418434">"ቅልም"</string>
-    <string name="vignette" msgid="7648125924662648282">"ቪጅኔት"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"ፎቶ ለመከርከም አመልካቾችን ጎትት"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"ፎቶውን doodle ለማድረግ ሳል"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"ፎቶውን በፍጥነት ለመግለጥ ጎትት"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"ቀይ አይኖችን ለማስወገድ እነሱ ላይ መታ አድርግ"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"ፎቶውንለማሽከርከር ጎትት"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"ፎቶውንቀጥ ለማደረግ ጎትት"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"የተጋላጭነት ተፅዕኖዎች"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"የቀለም ተፅዕኖዎች"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"ስነ-ጥበባዊ ተፅዕኖዎች"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"ጠግን"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"ቀልብስ"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"ዳግም አድርግ"</string>
-</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 3da6c9e..1d9fab6 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"ወደ ቀኝ አሽከርክር"</string>
     <string name="no_such_item" msgid="5315144556325243400">"ሊገኙ አልተቻለም::"</string>
     <string name="edit" msgid="1502273844748580847">"አርትዕ"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"ቀላል አርትዖት"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"መሸጎጫ ጥየቃዎች ሂደት"</string>
     <string name="caching_label" msgid="4521059045896269095">"በመሸጎጥ ላይ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"ከርክም"</string>
     <string name="trim_action" msgid="703098114452883524">"አሳጥር"</string>
+    <string name="mute_action" msgid="5296241754753306251">"ድምጽ-ከል አድርግ"</string>
     <string name="set_as" msgid="3636764710790507868">"እንደ"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"ቪዲዮ ላይ ድምጸ-ከል ማድረግ አልተቻለም።"</string>
     <string name="video_err" msgid="7003051631792271009">"ቪዲዮ ማጫወት አይቻልም።"</string>
     <string name="group_by_location" msgid="316641628989023253">"በስፍራ"</string>
     <string name="group_by_time" msgid="9046168567717963573">"በጊዜ"</string>
@@ -113,7 +116,7 @@
     <string name="no_albums_alert" msgid="4111744447491690896">"ምንም አልበሞች አልተገኙም::"</string>
     <string name="empty_album" msgid="4542880442593595494">"O ምስሎች/ ቪዲዮዎች ማግኘት ይቻላል::"</string>
     <string name="picasa_posts" msgid="1497721615718760613">"ልጥፎች"</string>
-    <string name="make_available_offline" msgid="5157950985488297112">"ከመስመር ውጪ እንዲገኝአድርግ"</string>
+    <string name="make_available_offline" msgid="5157950985488297112">"ከመስመር ውጪ እንዲገኝ አድርግ"</string>
     <string name="sync_picasa_albums" msgid="8522572542111169872">"አድስ"</string>
     <string name="done" msgid="217672440064436595">"ተከናውኗል"</string>
     <string name="sequence_in_set" msgid="7235465319919457488">"%1$d  ከ%2$d  አይነቶች:"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"ራስ ሰር"</string>
     <string name="flash_on" msgid="7891556231891837284">"ብልጭ ብሏል"</string>
     <string name="flash_off" msgid="1445443413822680010">"ምንም ብልጭታ"</string>
+    <string name="unknown" msgid="3506693015896912952">"አይታወቅም"</string>
     <string name="ffx_original" msgid="372686331501281474">"የመጀመሪያው"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"ወይን"</string>
     <string name="ffx_instant" msgid="726968618715691987">"ፈጣን"</string>
@@ -172,7 +176,7 @@
     <string name="click_import" msgid="6407959065464291972">"ለማስገባት እዚህ ንካ"</string>
     <string name="widget_type_album" msgid="6013045393140135468">"አልበም ምረጥ"</string>
     <string name="widget_type_shuffle" msgid="8594622705019763768">"ሁሉንም  ምስሎች  በውዝ"</string>
-    <string name="widget_type_photo" msgid="6267065337367795355">"ምስል ምረጥ"</string>
+    <string name="widget_type_photo" msgid="6267065337367795355">"ምስል ይምረጡ"</string>
     <string name="widget_type" msgid="1364653978966343448">"ምስሎችን ምረጥ"</string>
     <string name="slideshow_dream_name" msgid="6915963319933437083">"ስላይድ ትዕይንት"</string>
     <string name="albums" msgid="7320787705180057947">"አልበሞች"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"ምንም ውጫዊ ማከማቻ የለም"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"የድርድር ፊልም እይታ"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"የፍርግርግ ዕይታ"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"የሙሉ ማያገጽ እይታ"</string>
     <string name="trimming" msgid="9122385768369143997">"ማሳጠር"</string>
+    <string name="muting" msgid="5094925919589915324">"ድምጸ-ከል በማድረግ ላይ"</string>
     <string name="please_wait" msgid="7296066089146487366">"እባክዎ ይጠብቁ"</string>
-    <string name="save_into" msgid="4960537214388766062">"ያጠረ ቪዲዮ ወደ አልበም በማስቀመጥ ላይ፦"</string>
+    <string name="save_into" msgid="9155488424829609229">"ቪዲዮ ለ<xliff:g id="ALBUM_NAME">%1$s</xliff:g> በማጋራት ላይ …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"ማሳጠር አይቻልም፤ ዒላማው በጣም አጭር ነው"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"ፓኖራማን በማሳየት ላይ"</string>
     <string name="save" msgid="613976532235060516">"አስቀምጥ"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"ይዘትን በመቃኘት ላይ..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d ንጥሎች ተቃኝተዋል"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d ንጥል ተቃኝቷል"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d ንጥሎች ተቃኝተዋል"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"በመደርደር ላይ..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"ቅኝት ተጠናቅቋል"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"በማስመጣት ላይ…"</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"ወደዚህ መሳሪያ ሊመጣ የሚችል ምንም ሊገኝ የሚችል ይዘት የለም።"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"ምንም የተገናኘ የMTP መሳሪያ የለም"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"ካሜራ ስህተት"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"ከካሜራ ጋር ማገናኘት አልተቻለም።"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"በደህንነት ፖሊሲዎች ምክንያት ካሜራ ቦዝኗል።"</string>
+    <string name="camera_label" msgid="6346560772074764302">"ካሜራ"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"ካምኮርድ"</string>
+    <string name="wait" msgid="8600187532323801552">"እባክዎ ይጠብቁ…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"እባክህ ካሜራ ከመጠቀምህ በፊት USB ሰካ።"</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"እባክህ ካሜራውን ከመጠቀምህ በፊት የSD ካርድ አስገባ።"</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"የUSB ማከማቻ በማዘጋጀት ላይ..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"የ SD  ካርድ በማዘጋጀት ላይ..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USB ማከማችን መድረስ አልተቻለም፡፡"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SD ካርድን መድረስ አልተቻለም፡፡"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ይቅር"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ተከናውኗል"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"በመቅዳት የሚፈጀውን ጊዜ"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"ካሜራ ምረጥ"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"ተመለስ"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"የፊት"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"ሥፍራ"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"አካባቢ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"ሰዓት ቆጣሪ"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 ሰከንድ"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d ሰከንዶች"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"ጊዜ በሚቆጠርበት ጊዜ ድምጽ"</string>
+    <string name="setting_off" msgid="4480039384202951946">"ጠፍቷል"</string>
+    <string name="setting_on" msgid="8602246224465348901">"በርቷል"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"ቪዲዮ ጥራት"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"ከፍ ያለ"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"ዝቅ ያለ"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"አላፊ ጊዜ"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"የካሜራ ቅንብሮች"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"የካምኮርድ ቅንብሮች"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"የምስል መጠን"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13ሚ ፒክሰሎች"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 ሜጋፒክሰል"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 ሜጋ ፒክሴል"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4ሚ ፒክሰሎች"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 ሜጋ ፒክሴል"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 ሜጋ ፒክሴል"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2ሚ ፒክሰሎች (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3 ሜጋ ፒክሴል"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 ሜጋ ፒክሴል"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"የማተኮር ሁነታ"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"ራስ"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"ወሰን የሌለው"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"ማክሮ"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"ራስ-ሰር"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"የትየሌለ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"ማክሮ"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"የብልጭታ ሁነታ"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"የብልጭታ ሁነታ"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"ራስ"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"በ"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"ውጪ"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"ራስ-ብልጭታ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"ብልጭታ በርቷል"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"ብልጭታ ጠፍቷል"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"ዝግጁ ምስል"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"የነጭ ምጥጥን"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"ራስ"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"ያለፈበት"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"የቀን ብርሃን"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"ፍሎረሰንት"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"ደመናማ"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"ራስ-ሰር"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"የሙቀት ብርሃን"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"የቀን ብርሃን"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ፍሎረሰንት"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ደመናማ"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"የእይታ ሁነታ"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"ራስ"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"ተግባር"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"ማታ"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"ፀሀይ ስትጠልቅ"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"ፓርቲ"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ምንም"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"እርምጃ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ማታ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ፀሀይ ስትጠልቅ"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ድግስ"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ሰዓት ቆጣሪ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ሰዓት ቆጣሪ ጠፍቷል"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 ሰከንድ"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 ሰከንዶች"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 ሰከንዶች"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 ሰከንዶች"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">" በትዕይንት ሁኔታ መመረጥ የሚችል አይደለም።"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"የተጋለጠ"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ተጋላጭነት"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"ኤች ዲ አር"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"የፊት ካሜራ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"የኋላ ካሜራ"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"እሺ"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"የUSB  ማከማቻዎቦታ እየሞላበት ነው።የጥራት ቅንብር ይለውጡ ወይም አንዳንድ ምስሎችን ወይም ሌላ ፋይሎች ይሰርዙ።"</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"የSD ካርድዎ ቦታ እያለቀበት ነው። የጥራት ቅንብሩን ይልወጡ ወይም አንዳንድ ምስሎችንወይም ሌሎች ፋይሎችን ይሰርዙ።"</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"መጠኑ ላይ ደርሷል፡፡"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"በጣም ፈጥኖዋል"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"ፓናሮማ በማዘጋጀት ላይ"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"ፓኖራማ  ማስቀመጥ አልተቻለም::"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"ፓኖራማ"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"ፓኖራማ በማንሳት ላይ"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"ቀዳሚ ፓኖራማ በመጠበቅ ላይ"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"በማስቀመጥ ላይ&amp;hellip;"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"ፓኖራማን በማሳየት ላይ"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"ዳሰስ ለማተኮር፡፡"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"ማሳመሪያዎች"</string>
+    <string name="effect_none" msgid="3601545724573307541">"ምንም የለም"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"ጭመቅ"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"ትላልቅ ዓይኖች"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"ትልቅ አፍ"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"ትንሽ አፍ"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"ትልቅ አፍንጫ"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"ትናንሽ ዓይኖች"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"ቦታ ውስጥ"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"ፀሀይ ስትጠልቅ"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"ቪዲዮህ"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"መሳሪያህን ወደታች አኑር።"\n"  ለትንሽ ቆይታ ከዕይታ ውጪ ሁን።"</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"እየቀዳህ ፎቶ ለማንሳት ንካ።"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"የቪዲዮ ቀረጻ ተጀምሯል።"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"የቪዲዮ ቀረጻ ቆሟል።"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"ቪዲዮ ማንሻ ልዩ ማሳመሪያዎች ሲበሩ ይቦዝናል::"</string>
+    <string name="clear_effects" msgid="5485339175014139481">"ማሳመሪያዎች አጽዳ"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ሞኛሞኝ ፊቶች"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ዳራ"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"የካሜራ ሌንስ መከለያ አዝራር።"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"የምናሌ አዝራር"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"በጣም የቅርብ ፎቶ"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"የፊትና ኋላ ካሜራ ማብሪያና ማጥፊያ"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"ካሜራ፣ቪድዮ ወይም ፓናሮማ መምረጫ"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"ተጨማሪ ቅንብሮች  መቈጣጠሪያ"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"የቅንጅት መቆጣጠሪያዎች ዝጋ"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"ኣጕላ  መቆጣጠሪያ"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"ቀንስ %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"ጨምር %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s ምልክት ማድረጊያ ሳጥን"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"ወደ ፎቶ ቀይር"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"ወደ ቪዲዮ ቀይር"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"ወደ ፓኖራማ ቀይር"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"ወደ አዲስ ፓኖራማ ይቀይሩ"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"ግምገማ፣ ሰርዝ"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"ግምገማ፣ ተጠናቅቋል"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"ዳግም የተነሳውን ይገምግሙ"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"ቪዲዮ አጫውት"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"ቪዲዮ ለአፍታ አቁም"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"ቪዲዮ ዳግም ጫን"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"የቪዲዮ አጫዋች ሰዓት አሞሌ"</string>
+    <string name="capital_on" msgid="5491353494964003567">"በርቷል"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ጠፍቷል"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"ጠፍቷል"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 ሰከንድ"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 ሰከንዶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 ደቂቃ"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 ደቂቃዎች"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ሰዓት"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 ሰዓት"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ሰዓቶች"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ሰዓቶች"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"ሰኮንዶች"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"ደቂቃዎች"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ሰዓቶች"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"ተከናውኗል"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"የጊዜ ክፍተት ያዘጋጁ"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"የአላፊ ጊዜ ባህሪ ጠፍቷል። የጊዜ ክፍተቱን ለማዘጋጀት ያብሩት።"</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"የሰዓት ቆጣሪ ጠፍቷል። ስዕል ከማንሳትዎ በፊት ለመቁጠር ያብሩት።"</string>
+    <string name="set_duration" msgid="5578035312407161304">"ቆይታን በሰከንዶች ያዘጋጁ"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"ፎቶ ለመውሰድ ጊዜ በመቁጠር ላይ"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"የፎቶ አካባቢዎች ይታወሱ?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"ፎቶዎችዎን እና ቪዲዮዎችዎን በተነሱበት አካባቢዎች መለያ ይስጧቸው።"\n\n"ሌሎች መተግበሪያዎች ይህንን መረጃ ከተቀመጡ ምስሎችዎ ጋር ሊደርሱበት ይችላሉ።"</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"አይ፣ አመሰግናለሁ"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"አዎ"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"ካሜራ"</string>
+    <string name="menu_search" msgid="7580008232297437190">"ፍለጋ"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"ፎቶዎች"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"አልበሞች"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ተጨማሪ አማራጮች"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"ቅንብሮች"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d ፎቶ"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d ፎቶዎች"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ar/filtershow_strings.xml b/res/values-ar/filtershow_strings.xml
index d8db3fc..926136e 100644
--- a/res/values-ar/filtershow_strings.xml
+++ b/res/values-ar/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"لا يمكن تحميل الصورة!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"جارٍ تعيين الخلفية"</string>
+    <string name="download_failure" msgid="5923323939788582895">"تعذر تنزيل الصورة. الشبكة غير متاحة."</string>
     <string name="original" msgid="3524493791230430897">"أصلية"</string>
     <string name="borders" msgid="2067345080568684614">"حدود"</string>
-    <string name="done" msgid="3112344807927554662">"تم"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"تراجع"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"إعادة"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"عرض السجل"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"إخفاء السجل"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"عرض حالة الصورة"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"إخفاء حالة الصورة"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"إظهار التأثيرات المطبقة"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"إخفاء التأثيرات المطبقة"</string>
     <string name="menu_settings" msgid="6428291655769260831">"إعدادات"</string>
+    <string name="unsaved" msgid="8704442449002374375">"هناك تغييرات في هذه الصورة لم يتم حفظها."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"هل تريد الحفظ قبل الخروج؟"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"حفظ وخروج"</string>
+    <string name="exit" msgid="242642957038770113">"خروج"</string>
     <string name="history" msgid="455767361472692409">"السجل"</string>
     <string name="reset" msgid="9013181350779592937">"إعادة تعيين"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"حالة الصورة الحالية"</string>
+    <string name="imageState" msgid="8632586742752891968">"التأثيرات المطبقة"</string>
     <string name="compare_original" msgid="8140838959007796977">"مقارنة"</string>
     <string name="apply_effect" msgid="1218288221200568947">"تطبيق"</string>
     <string name="reset_effect" msgid="7712605581024929564">"إعادة تعيين"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"لا شيء"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"ثابتة"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"كوكب صغير"</string>
     <string name="exposure" msgid="6526397045949374905">"التعرض للضوء"</string>
     <string name="sharpness" msgid="6463103068318055412">"حدة"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"لون تلقائي"</string>
     <string name="hue" msgid="6231252147971086030">"تدرج اللون"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"ظلال"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"تسليط الضوء"</string>
     <string name="curvesRGB" msgid="915010781090477550">"المنحنيات"</string>
     <string name="vignette" msgid="934721068851885390">"نقوش صورة نصفية"</string>
     <string name="redeye" msgid="4508883127049472069">"العين الحمراء"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"رسم"</string>
     <string name="straighten" msgid="26025591664983528">"تسوية"</string>
     <string name="crop" msgid="5781263790107850771">"اقتصاص"</string>
     <string name="rotate" msgid="2796802553793795371">"تدوير"</string>
     <string name="mirror" msgid="5482518108154883096">"انعكاس"</string>
+    <string name="negative" msgid="6998313764388022201">"سالب"</string>
     <string name="none" msgid="6633966646410296520">"لا شيء"</string>
+    <string name="edge" msgid="7036064886242147551">"الحواف"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"تصغير حجم"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"حمراء"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"خضراء"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"زرقاء"</string>
+    <string name="draw_style" msgid="2036125061987325389">"النمط"</string>
+    <string name="draw_size" msgid="4360005386104151209">"الحجم"</string>
+    <string name="draw_color" msgid="2119030386987211193">"اللون"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"الأسطر"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"محدد"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"رشاش"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"محو"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"اختيار لون مخصص"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"تحديد اللون"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"تحديد الحجم"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"موافق"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"أصلي"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"النتيجة"</string>
 </resources>
diff --git a/res/values-ar/photoeditor_strings.xml b/res/values-ar/photoeditor_strings.xml
deleted file mode 100644
index 2d7cd81..0000000
--- a/res/values-ar/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"تعذر تحميل الصورة"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"تعذر حفظ الصورة المعدلة"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"تم حفظ الصورة المعدلة في <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"هل تريد إلغاء أي تغييرات غير محفوظة؟"</string>
-    <string name="yes" msgid="5402582493291792293">"نعم"</string>
-    <string name="save" msgid="5516670392524294967">"حفظ"</string>
-    <string name="autofix" msgid="1663414996270538748">"إصلاح تلقائي"</string>
-    <string name="crop" msgid="7598378507763334041">"اقتصاص"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"معالجة متقاطعة"</string>
-    <string name="documentary" msgid="50396326708699797">"وثائقي"</string>
-    <string name="doodle" msgid="1686409894518940990">"رسومات مبتكرة"</string>
-    <string name="duotone" msgid="8145893940788467106">"درجة لونين"</string>
-    <string name="facelift" msgid="6205748523156172637">"وهج الوجه"</string>
-    <string name="facetan" msgid="4412831806626044111">"سُمرة الوجه"</string>
-    <string name="filllight" msgid="2644989991700022526">"إضاءة تكميلية"</string>
-    <string name="fisheye" msgid="6037488646928998921">"عين سمكة"</string>
-    <string name="flip" msgid="2357692401826287480">"عكس"</string>
-    <string name="grain" msgid="7487585304579789098">"تحبب الفيلم"</string>
-    <string name="grayscale" msgid="7641563843060945228">"أبيض وأسود"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"أبيض وأسود"</string>
-    <string name="highlight" msgid="3902653944386623972">"لمحات"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"سلبي"</string>
-    <string name="posterize" msgid="4139212359561383385">"تتالٍ"</string>
-    <string name="redeye" msgid="4958448806369928239">"العين الحمراء"</string>
-    <string name="rotate" msgid="6607597269792373083">"تدوير"</string>
-    <string name="saturation" msgid="8621322012271169931">"تشبع اللون"</string>
-    <string name="sepia" msgid="7978093531824705601">"بني داكن"</string>
-    <string name="shadow" msgid="8235188588101973090">"ظلال"</string>
-    <string name="sharpen" msgid="8449662378104403230">"زيادة الحدة"</string>
-    <string name="straighten" msgid="5217801513491493491">"تسوية"</string>
-    <string name="temperature" msgid="1607987938521534517">"دفء الألوان"</string>
-    <string name="tint" msgid="154435943863418434">"تلوين خفيف"</string>
-    <string name="vignette" msgid="7648125924662648282">"نقوش صورة نصفية"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"اسحب العلامات لاقتصاص الصورة"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"اسحب الصورة لإنشاء رسومات مبتكرة"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"اسحب الصورة لقلبها"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"انقر على العيون الحمراء لإزالتها"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"اسحب الصورة لتدويرها"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"اسحب الصورة لتسويتها"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"تأثيرات التعرض للضوء"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"تأثيرات الألوان"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"التأثيرات الفنية"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"إصلاح"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"تراجع"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"إعادة"</string>
-</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 70a1b58..e39df49 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"تدوير لليمين"</string>
     <string name="no_such_item" msgid="5315144556325243400">"تعذر العثور على العنصر."</string>
     <string name="edit" msgid="1502273844748580847">"تعديل"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"تعديل بسيط"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"معالجة طلبات التخزين المؤقت"</string>
     <string name="caching_label" msgid="4521059045896269095">"تخزين مؤقت..."</string>
     <string name="crop_action" msgid="3427470284074377001">"اقتصاص"</string>
     <string name="trim_action" msgid="703098114452883524">"اقتطاع"</string>
+    <string name="mute_action" msgid="5296241754753306251">"كتم الصوت"</string>
     <string name="set_as" msgid="3636764710790507868">"تعيين كـ"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"لا يمكن كتم صوت الفيديو."</string>
     <string name="video_err" msgid="7003051631792271009">"لا يمكن تشغيل الفيديو."</string>
     <string name="group_by_location" msgid="316641628989023253">"بحسب الموقع"</string>
     <string name="group_by_time" msgid="9046168567717963573">"بحسب الوقت"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"تلقائية"</string>
     <string name="flash_on" msgid="7891556231891837284">"تم تشغيل الفلاش"</string>
     <string name="flash_off" msgid="1445443413822680010">"بلا فلاش"</string>
+    <string name="unknown" msgid="3506693015896912952">"غير معروف"</string>
     <string name="ffx_original" msgid="372686331501281474">"أصلية"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"فوري"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Bleach"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"أزرق"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"أبيض/أسود"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"لا تتوفر سعة تخزين خارجية"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"عرض شريط الفيلم"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"عرض الشبكة"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"عرض ملء الشاشة"</string>
     <string name="trimming" msgid="9122385768369143997">"جارٍ الاقتطاع"</string>
+    <string name="muting" msgid="5094925919589915324">"جارِ كتم الصوت"</string>
     <string name="please_wait" msgid="7296066089146487366">"الرجاء الانتظار"</string>
-    <string name="save_into" msgid="4960537214388766062">"جارٍ حفظ مقطع الفيديو المقتطع في ألبوم:"</string>
+    <string name="save_into" msgid="9155488424829609229">"جارٍ حفظ الفيديو <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"لا يمكن الاقتطاع: الفيديو المستهدف قصير جدًا"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"جارٍ عرض البانوراما"</string>
     <string name="save" msgid="613976532235060516">"حفظ"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"جارٍ مسح المحتوى ضوئيًا..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"تم مسح %1$d من العناصر ضوئيًا"</item>
+    <item quantity="one" msgid="4340019444460561648">"تم مسح %1$d عنصر ضوئيًا"</item>
+    <item quantity="other" msgid="3138021473860555499">"تم مسح %1$d من العناصر ضوئيًا"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"جارٍ التصنيف..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"تم المسح الضوئي"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"جارٍ الاستيراد..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"لا يتوفر أي محتوى لاستيراده على هذا الجهاز."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"ليس هناك جهاز MTP متصل"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"خطأ في الكاميرا"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"يتعذر الاتصال بالكاميرا."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"تم تعطيل الكاميرا بسبب سياسات الأمان."</string>
+    <string name="camera_label" msgid="6346560772074764302">"الكاميرا"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"كاميرا فيديو"</string>
+    <string name="wait" msgid="8600187532323801552">"يرجى الانتظار…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"حمِّل وحدة تخزين USB قبل استخدام الكاميرا."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"أدرج بطاقة SD قبل استخدام الكاميرا."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"جارٍ تحضير وحدة تخزين USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"جارٍ تحضير بطاقة SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"تعذر الدخول إلى وحدة تخزين USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"تعذر الدخول إلى بطاقة SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"إلغاء"</string>
+    <string name="review_ok" msgid="1156261588693116433">"تم"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"تسجيل اللقطات المتتابعة"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"اختيار كاميرا"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"رجوع"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"الأمام"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"تخزين الموقع"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"الموقع"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"مؤقت العد التنازلي"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"ثانية واحدة"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d ثانية"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"صفير أثناء العد التنازلي"</string>
+    <string name="setting_off" msgid="4480039384202951946">"إيقاف"</string>
+    <string name="setting_on" msgid="8602246224465348901">"تشغيل"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"جودة الفيديو"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"مرتفعة"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"منخفضة"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"لقطات متتابعة"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"إعدادات الكاميرا"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"إعدادات كاميرا الفيديو"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"حجم الصورة"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 ميغابكسل"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 ميغا بكسل (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 ميغا بكسل"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"وضع التركيز"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"تلقائي"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"بلا نهاية"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"ماكرو"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"تلقائي"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"بلا نهاية"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"ماكرو"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"وضع الفلاش"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"وضع الفلاش"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"تلقائي"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"تشغيل"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"إيقاف"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"الفلاش التلقائي"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"تشغيل الفلاش"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"إيقاف الفلاش"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"موازنة اللون الأبيض"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"توازن الأبيض"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"تلقائي"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"براق"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"نهاري"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"فلورسنت"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"غائم"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"تلقائي"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"براق"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"نهاري"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"فلورسنت"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"غائم"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"وضع المشهد"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"تلقائي"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"النطاق الديناميكي العالي"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"الإجراء"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"ليلي"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"الغروب"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"مجموعة"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"لا شيء"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"حركة"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ليلاً"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"الغروب"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"حفلة"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"موقّت العد التنازلي"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"إيقاف الموقّت"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"ثانية واحدة"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 ثوانٍ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 ثوانٍ"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 ثانية"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"لا يمكن تحديده في وضع المشهد."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"التعرض"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"التعرض للضوء"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"نطاق عالي الديناميكية"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"الكاميرا الأمامية"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"الكاميرا الخلفية"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"موافق"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"مساحة وحدة تخزين USB منخفضة. غيّر إعداد الجودة أو احذف بعض الصور أو الملفات الأخرى."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"مساحة بطاقة SD منخفضة. غيّر إعداد الجودة أو احذف بعض الصور أو الملفات الأخرى."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"تم بلوغ الحد الأقصى للحجم."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"سريع للغاية"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"جارٍ تحضير العرض البانورامي"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"تعذر حفظ بانوراما."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"عرض بانورامي"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"جارٍ التقاط عرض بانورامي"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"في انتظار العرض البانورامي السابق"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"جارٍ الحفظ..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"جارٍ عرض البانوراما"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"المس للتركيز."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"تأثيرات"</string>
+    <string name="effect_none" msgid="3601545724573307541">"بلا"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"ضغط"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"عيون كبيرة"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"فم كبير"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"فم صغير"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"أنف كبير"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"عيون صغيرة"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"في الفضاء"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"الغروب"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"مقطع فيديو"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"ضع جهازك أسفل"\n"اخرج من العرض للحظة."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"المس لالتقاط صورة أثناء التسجيل."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"بدأ تسجيل الفيديو."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"تم إيقاف تسجيل الفيديو."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"يتم تعطيل لقطة الفيديو عند تشغيل التأثيرات الخاصة."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"محو التأثيرات"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"وجوه مضحكة"</string>
+    <string name="effect_background" msgid="6579360207378171022">"الخلفية"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"زر المصراع"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"زر القائمة"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"أحدث صورة"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"مفتاح التبديل بين الأمام والخلف"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"محدّد الكاميرا أو الفيديو أو البانوراما"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"مزيد من عناصر التحكم في الإعدادات"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"إغلاق عناصر التحكم في الإعدادات"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"التحكم في التكبير/التصغير"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"خفض %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"زيادة %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"مربع اختيار %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"تبديل إلى وضع الصور"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"تبديل إلى الفيديو"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"تبديل إلى بانوراما"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"تبديل إلى بانوراما جديدة"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"مراجعة الإلغاء"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"تمت المراجعة"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"مراجعة: إعادة الالتقاط"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"تشغيل الفيديو"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"إيقاف الفيديو مؤقتًا"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"إعادة تحميل الفيديو"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"شريط الوقت لمشغّل الفيديو"</string>
+    <string name="capital_on" msgid="5491353494964003567">"تشغيل"</string>
+    <string name="capital_off" msgid="7231052688467970897">"إيقاف"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"تم الإيقاف"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"نصف ثانية"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"ثانية واحدة"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"ثانية ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"ثانيتان"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"ثانيتان ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 ثوانٍ"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 ثوانٍ"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 ثوانٍ"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 ثوانٍ"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 ثوانٍ"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 ثانية"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 ثانية"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 ثانية"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"نصف دقيقة"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"دقيقة واحدة"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"دقيقة ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"دقيقتان"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"دقيقتان ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 دقائق"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 دقائق"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 دقائق"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 دقائق"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 دقائق"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 دقيقة"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 دقيقة"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 دقيقة"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"نصف ساعة"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"ساعة واحدة"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"ساعة ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"ساعتان"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"ساعتان ونصف"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ساعات"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ساعات"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ساعات"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ساعات"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ساعات"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ساعة"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ساعة"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ساعة"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"ثوانٍ"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"دقائق"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ساعات"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"تم"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"تعيين المهلة الزمنية"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"ميزة اللقطات المتتابعة مغلقة. يمكنك تشغيلها لتعيين المهلة الزمنية."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"مؤقت العد التنازلي متوقف. يُمكنك تشغيله لإجراء العد التنازلي قبل التقاط صورة."</string>
+    <string name="set_duration" msgid="5578035312407161304">"تعيين المدة بالثواني"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"العد التنازلي لالتقاط صورة"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"هل تتذكر مواقع الصور؟"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"ضع علامة على الصور ومقاطع الفيديو التابعة لك تشير إلى المواقع التي تم التقاطها منها."\n\n"يمكن لتطبيقات أخرى الدخول إلى هذه المعلومات إلى جانب صورك المحفوظة."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"لا، شكرًا"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"نعم"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"الكاميرا"</string>
+    <string name="menu_search" msgid="7580008232297437190">"البحث"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"الصور"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"الألبومات"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"خيارات إضافية"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"إعدادات"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d صورة"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d من الصور"</item>
+  </plurals>
 </resources>
diff --git a/res/values-be/filtershow_strings.xml b/res/values-be/filtershow_strings.xml
index 55472bb..049ba6e 100644
--- a/res/values-be/filtershow_strings.xml
+++ b/res/values-be/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Не атрымлiваецца загрузіць малюнак"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Усталёўка шпалер..."</string>
+    <string name="download_failure" msgid="5923323939788582895">"Не атрымалася спампаваць фота. Сетка недаступная."</string>
     <string name="original" msgid="3524493791230430897">"Арыгiнал"</string>
     <string name="borders" msgid="2067345080568684614">"Межы"</string>
-    <string name="done" msgid="3112344807927554662">"Гатова"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Вярнуць"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Паўтарыць"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Паказаць гісторыю"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Схаваць гісторыю"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Паказаць статус малюнка"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Схаваць статус малюнка"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Паказваць прымененыя эфекты"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Хаваць прымененыя эфекты"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Налады"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Існуюць незахаваныя змяненні ў гэтай выяве."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Жадаеце захавацца перад выхадам?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Захаваць і выйсці"</string>
+    <string name="exit" msgid="242642957038770113">"Выйсці"</string>
     <string name="history" msgid="455767361472692409">"Гісторыя"</string>
     <string name="reset" msgid="9013181350779592937">"Скінуць"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Бягучы стан малюнка"</string>
+    <string name="imageState" msgid="8632586742752891968">"Прымененыя эфекты"</string>
     <string name="compare_original" msgid="8140838959007796977">"Параўнаць"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Паспрабаваць"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Скінуць"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Няма"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Выпраўленыя"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Маленькая планета"</string>
     <string name="exposure" msgid="6526397045949374905">"Экспазіцыя"</string>
     <string name="sharpness" msgid="6463103068318055412">"Выразнасць"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Aўтаколер"</string>
     <string name="hue" msgid="6231252147971086030">"Тон"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Цені"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Блікі"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Пэндзаль"</string>
     <string name="vignette" msgid="934721068851885390">"Віньетка"</string>
     <string name="redeye" msgid="4508883127049472069">"Чырвонае вока"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Намаляваць"</string>
     <string name="straighten" msgid="26025591664983528">"Выраўнаваць"</string>
     <string name="crop" msgid="5781263790107850771">"Абрэзаць"</string>
     <string name="rotate" msgid="2796802553793795371">"Павярнуць"</string>
     <string name="mirror" msgid="5482518108154883096">"Люстра"</string>
+    <string name="negative" msgid="6998313764388022201">"Негатыў"</string>
     <string name="none" msgid="6633966646410296520">"Няма"</string>
+    <string name="edge" msgid="7036064886242147551">"Краi"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Уорхал"</string>
+    <string name="downsample" msgid="3552938534146980104">"Паменшыць"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Чырвоны"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Зялёны"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Сiнi"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Стыль"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Памер"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Колер"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Лiнii"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Маркёр"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Пырскi"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Ачысціць"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Выбраць іншы колер"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Выберыце колер"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Выберыце памер"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Арыгiнал"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Вынiк"</string>
 </resources>
diff --git a/res/values-be/photoeditor_strings.xml b/res/values-be/photoeditor_strings.xml
deleted file mode 100644
index 2eb8b56..0000000
--- a/res/values-be/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Фотастудыя"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Немагчыма загрузіць фота"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Немагчыма захаваць адрэдагаванае фота"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Адрэдагаванае фота захавана ў тэчцы <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Скасаваць незахаваныя змены?"</string>
-    <string name="yes" msgid="5402582493291792293">"Так"</string>
-    <string name="save" msgid="5516670392524294967">"ЗАХАВАЦЬ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Аўтафікс."</string>
-    <string name="crop" msgid="7598378507763334041">"Абрэзаць"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Крос-апрацоўка"</string>
-    <string name="documentary" msgid="50396326708699797">"Дакументальнае"</string>
-    <string name="doodle" msgid="1686409894518940990">"Святоч. лагатып"</string>
-    <string name="duotone" msgid="8145893940788467106">"Дуа-тон"</string>
-    <string name="facelift" msgid="6205748523156172637">"Ззянне"</string>
-    <string name="facetan" msgid="4412831806626044111">"Загар"</string>
-    <string name="filllight" msgid="2644989991700022526">"Заліць святлом"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Рыб\'е вока"</string>
-    <string name="flip" msgid="2357692401826287480">"Разварот"</string>
-    <string name="grain" msgid="7487585304579789098">"Зерне плёнкі"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Чорна-белы"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Чорна-белыя выявы"</string>
-    <string name="highlight" msgid="3902653944386623972">"Блікі"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Негатыў"</string>
-    <string name="posterize" msgid="4139212359561383385">"Пастэрызаваць"</string>
-    <string name="redeye" msgid="4958448806369928239">"Чырвонае вока"</string>
-    <string name="rotate" msgid="6607597269792373083">"Павярнуць"</string>
-    <string name="saturation" msgid="8621322012271169931">"Насычанасць"</string>
-    <string name="sepia" msgid="7978093531824705601">"Сэпія"</string>
-    <string name="shadow" msgid="8235188588101973090">"Цені"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Павял. рэзкасць"</string>
-    <string name="straighten" msgid="5217801513491493491">"Выпраміць"</string>
-    <string name="temperature" msgid="1607987938521534517">"Цеплыня"</string>
-    <string name="tint" msgid="154435943863418434">"Адценне"</string>
-    <string name="vignette" msgid="7648125924662648282">"Віньеткі"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Перацягнуць маркеры, каб абрэзаць фота"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Перац. фота, каб пераўтв. яго ў святоч. лагатып"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Перацягніце фатаграфію, каб перавярнуць яе"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Нац. на \"чырвоныя вочы\", каб выдаліць іх"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Перацягніце фатаграфію, каб павярнуць яе"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Перацягніце фатаграфію, каб выпраміць яе"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Эфекты экспазiцыi"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Каляровыя эфекты"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Мастацкія эфекты"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Паправiць"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Вярнуць"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Паўтарыць"</string>
-</resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index aa0b061..0974d2a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Павярнуць направа"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Элемент не знойдзены."</string>
     <string name="edit" msgid="1502273844748580847">"Рэдагаваць"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Простае рэдагаванне"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Запыты на кэшаванне працэсу"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кэшаванне..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Абрэзаць"</string>
     <string name="trim_action" msgid="703098114452883524">"Рамка"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Адключыць гук"</string>
     <string name="set_as" msgid="3636764710790507868">"Ўсталяваць у якасці"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Немагчыма адключыць вiдэа."</string>
     <string name="video_err" msgid="7003051631792271009">"Не атрымліваецца прайграць відэа."</string>
     <string name="group_by_location" msgid="316641628989023253">"Па месцазнаходжанні"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Па часе"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Аўта"</string>
     <string name="flash_on" msgid="7891556231891837284">"Са ўспышкай"</string>
     <string name="flash_off" msgid="1445443413822680010">"Без успышкі"</string>
+    <string name="unknown" msgid="3506693015896912952">"Невядома"</string>
     <string name="ffx_original" msgid="372686331501281474">"Арыгiнал"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Няма даступных знешнiх захавальнiкаў"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Дыястужка"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"У выглядзе табліцы"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Поўнаэкранны прагляд"</string>
     <string name="trimming" msgid="9122385768369143997">"Абрэзка"</string>
+    <string name="muting" msgid="5094925919589915324">"Бязгучна"</string>
     <string name="please_wait" msgid="7296066089146487366">"Пачакайце"</string>
-    <string name="save_into" msgid="4960537214388766062">"Захаванне абрэзанага відэа ў альбоме :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Захаванне відэа ў альбоме <xliff:g id="ALBUM_NAME">%1$s</xliff:g>..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"Немагчыма абрэзаць: мэтавае вiдэа занадта кароткае"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Атрыманне панарамы"</string>
     <string name="save" msgid="613976532235060516">"Захаваць"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Сканіраванне змесціва..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Сканіравана элементаў: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"Сканіраваны %1$d элемент"</item>
+    <item quantity="other" msgid="3138021473860555499">"Сканiравана элементаў: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Сартаванне..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Сканіраванне скончана"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Імпарт..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Няма кантэнту, даступнага для імпарту на гэтай прыладзе."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Няма MTP, падключанага да прылады"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Памылка камеры"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Не атрымлiваецца падключыцца да камеры."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Камера адключана з-за палітыкі бяспекі."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Камера"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Відэакамера"</string>
+    <string name="wait" msgid="8600187532323801552">"Чакайце..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Перш чым выкарыстоўваць камеру, падключыце USB-назапашвальнiк."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Устаўце SD-карту перад выкарыстаннем камеры."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Падрыхтоўка USB-назапашвальнiка..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Падрыхтоўка SD-карты..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Немагчыма атрымаць доступ да USB-назапашвальніка."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Немагчыма атрымаць доступ да SD-карты."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"АДМЯНІЦЬ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ГАТОВА"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Запіс на працягу доўгага часу"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Выбраць камеру"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Назад"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Перад"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Месцазнаходжанне крамы"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"МЕСЦАЗНАХОДЖАННЕ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Таймер зваротнага адлiку"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 секунда"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d секунд"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Гукавы сігнал падчас зваротнага адлiку"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Адключана"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Уключана"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Якасць відэа"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Высокая якасць"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Нізкая якасць"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Часавы інтэрвал"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Налады камеры"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Налады відэакамеры"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Памер малюнка"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 млн. пікселяў"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 мегапікселяў"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 мегапiкселяў"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 мiльёны пікселяў"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 мегапiкселя"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 мегапiкселя"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 мiльёны пікселяў (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 мегапікселя"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 мегапiксель"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Рэжым факусоўкі"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Аўтаматычна"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Бясконцасць"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Макра"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"АЎТАМАТЫЧНАЯ"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"БЯСКОНЦА"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"МАКРА"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Рэжым успышкі"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"РЭЖЫМ УСПЫШКI"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Аўтаматычна"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Уключана"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Адключана"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"АЎТАЎСПЫШКА"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"УСПЫШКА ЎКЛ."</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"УСПЫШ. ВЫКЛ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Баланс белага"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"БАЛАНС БЕЛАГА"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Аўтаматычна"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Белы напал"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Дзённае святло"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Флюарэсцэнтны"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Пахмурна"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"АЎТАМАТЫЧНАЯ"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"БЕЛЫ НАПАЛ"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ДЗЁННАЕ СВЯТЛО"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ФЛЮАРЭСЦЭНТНЫ"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ПАХМУРНА"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Рэжым здымкаў"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Аўтаматычна"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Дзеянне"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ноч"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Заход"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Вечарына"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"НЯМА"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ДЗЕЯННЕ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"НОЧ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ЗАХОД"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ВЕЧАРЫНА"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ТАЙМЕР ЗВАРОТНАГА АДЛІКУ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ТАЙМЕР АДКЛЮЧАНЫ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 СЕКУНДА"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 СЕКУНДЫ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 СЕКУНД"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 СЕКУНД"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Немагчыма выбраць у рэжыме здымкi."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Экспазіцыя"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ЭКСПАЗIЦЫЯ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ПЯРЭДНЯЯ КАМЕРА"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ЗАДНЯЯ КАМЕРА"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"ОК"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Месца на вашым USB-назапашвальнiку заканчваецца. Змяніце параметры якасці або выдаліце некаторыя выявы цi іншыя файлы."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Месца на вашай SD-карце заканчваецца. Змяніце параметры якасці або выдаліце некаторыя выявы цi іншыя файлы."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Дасягнуты максімальны памер."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Занад. хутка"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Падрыхтоўка панарамы"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Немагчыма захаваць панараму."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Панарама"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Захоп панарамы"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Чаканне папярэдняй панарамы"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Захаванне..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Рэндэрынг панарамы"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Націсніце, каб наладзiць фокус."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Эфекты"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Няма"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Сцicнуць"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Вялiкiя вочы"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Вялiкi рот"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Маленькi рот"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Вялікі нос"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Маленькiя вочы"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"У космасе"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Заход"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Вашы відэа"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Наладзьце прыладу."\n"На хвіліну выйдзіце з поля зроку."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Націсніце, каб зрабiць здымак падчас запісу."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Відэазапіс пачаўся."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Вiдэазапiс спыніўся."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Капiяванне кадра з відэа адключаецца, калі ўключаны спецэфекты."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Выдалiць эфекты"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ПАЦЕШНЫЯ СМАЙЛIКI"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ФОНАВЫЯ"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Кнопка затвора"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Кнопка меню"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Апошнія фатаграфii"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Пераключэнне пярэдняй i задняй камеры"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Камера, вiдэа або панарама"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Іншыя налады"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Закрыць налады"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Кiраванне маштабаваннем..."</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Памяншэнне на %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Павелічэнне %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Поле %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Пераключыцца на фота"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Пераключыцца ў рэжым відэа"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Пераключыцца ў рэжым панарамы"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Пераключыцца на новую панараму"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Адмена агляда"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Агляд зроблены"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Пераробка агляда"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Прайграванне відэа"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Паўза прайгравання відэа"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Абнавіць відэа"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Радок часу прайгравання відэапрайгравальніка"</string>
+    <string name="capital_on" msgid="5491353494964003567">"УКЛ."</string>
+    <string name="capital_off" msgid="7231052688467970897">"АДКЛ."</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Адключана"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 секунда"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 секунды"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 хвіліна"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 хвілін"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 хвілін"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 хвілін"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 хвілін"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 хвілін"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 хвіліны"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 гадзіна"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 гадзіны"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 гадзін"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 гадзін"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 гадзін"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 гадзін"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 гадзін"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 гадзіны"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"секунды"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"хвілін"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"г."</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Гатова"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Задаць iнтэрвал часу"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Функцыя \"Часавы інтэрвал\" выключана. Уключыце яе."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Таймер выключаны. Уключыце на iм зваротны адлік перад здымкай."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Усталяваць час у секундах"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Зваротны адлiк, каб зрабіць здымак"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Запамiнаць месца, дзе зроблена фота?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Адзначце на вашых фатаграфіях і відэа месцы, у якіх яны былі створаныя."\n\n"Іншыя праграмы могуць атрымаць доступ да гэтай інфармацыі разам з захаванымі выявамі."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Не, дзякуй"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Так"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Пошук"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Фатаграфіі"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Альбомы"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ДАДАТКОВЫЯ ПАРАМЕТРЫ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"НАЛАДЫ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d фота"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d фота"</item>
+  </plurals>
 </resources>
diff --git a/res/values-bg/filtershow_strings.xml b/res/values-bg/filtershow_strings.xml
index b651811..cbba456 100644
--- a/res/values-bg/filtershow_strings.xml
+++ b/res/values-bg/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Изображението не може да се зареди!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Тапетът се задава"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Снимката не можа да се изтегли. Мрежата не е налице."</string>
     <string name="original" msgid="3524493791230430897">"Оригинал"</string>
     <string name="borders" msgid="2067345080568684614">"Контури"</string>
-    <string name="done" msgid="3112344807927554662">"Готово"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Отмяна"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Възстановяване"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"История: Показване"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"История: Скриване"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Съст. на изобр.: Показване"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Съст. на изобр.: Скриване"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Приложени ефекти: Показване"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Приложени ефекти: Скриване"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Настройки"</string>
+    <string name="unsaved" msgid="8704442449002374375">"В това изображение има незапазени промени."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Искате ли да запазите преди изход?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Запазване и изход"</string>
+    <string name="exit" msgid="242642957038770113">"Изход"</string>
     <string name="history" msgid="455767361472692409">"История"</string>
     <string name="reset" msgid="9013181350779592937">"Повторно задаване"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Текущо състояние на изображението"</string>
+    <string name="imageState" msgid="8632586742752891968">"Приложени ефекти"</string>
     <string name="compare_original" msgid="8140838959007796977">"Сравняване"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Прилагане"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Нулиране"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Без"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Фиксирано"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Мини планета"</string>
     <string name="exposure" msgid="6526397045949374905">"Експониране"</string>
     <string name="sharpness" msgid="6463103068318055412">"Отчетливост"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Авт. цвят"</string>
     <string name="hue" msgid="6231252147971086030">"Нюанс"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Засенчване"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Просветляване"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Извивки"</string>
     <string name="vignette" msgid="934721068851885390">"Винетиране"</string>
     <string name="redeye" msgid="4508883127049472069">"Червени очи"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Рисуване"</string>
     <string name="straighten" msgid="26025591664983528">"Изправяне"</string>
     <string name="crop" msgid="5781263790107850771">"Подрязване"</string>
     <string name="rotate" msgid="2796802553793795371">"Завъртане"</string>
     <string name="mirror" msgid="5482518108154883096">"Огледало"</string>
+    <string name="negative" msgid="6998313764388022201">"Негатив"</string>
     <string name="none" msgid="6633966646410296520">"Без"</string>
+    <string name="edge" msgid="7036064886242147551">"Контури"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Уорхол"</string>
+    <string name="downsample" msgid="3552938534146980104">"Децимация"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"червено"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"зелено"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"синьо"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Стил"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Размер"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Цвят"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Линии"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Маркер"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Пръски боя"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Изчистване"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Избор на персонализиран цвят"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Избиране на цвят"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Избиране на размер"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Оригинал"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Резултат"</string>
 </resources>
diff --git a/res/values-bg/photoeditor_strings.xml b/res/values-bg/photoeditor_strings.xml
deleted file mode 100644
index c661c9f..0000000
--- a/res/values-bg/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Снимката не можа да бъде заредена"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Редакт. снимка не можа да бъде запазена"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Редактираната снимка е запазена в/ъв „<xliff:g id="FOLDER_NAME">%s</xliff:g>“"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Да се отхвърлят ли незапазените промени?"</string>
-    <string name="yes" msgid="5402582493291792293">"Да"</string>
-    <string name="save" msgid="5516670392524294967">"ЗАПАЗВ."</string>
-    <string name="autofix" msgid="1663414996270538748">"Автокоригиране"</string>
-    <string name="crop" msgid="7598378507763334041">"Подрязване"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Кроспроцес"</string>
-    <string name="documentary" msgid="50396326708699797">"Документални"</string>
-    <string name="doodle" msgid="1686409894518940990">"Рисунка"</string>
-    <string name="duotone" msgid="8145893940788467106">"В два цвята"</string>
-    <string name="facelift" msgid="6205748523156172637">"Руменина на лицето"</string>
-    <string name="facetan" msgid="4412831806626044111">"Тен на лицето"</string>
-    <string name="filllight" msgid="2644989991700022526">"Запълв. светл."</string>
-    <string name="fisheye" msgid="6037488646928998921">"Рибешко око"</string>
-    <string name="flip" msgid="2357692401826287480">"Обръщане"</string>
-    <string name="grain" msgid="7487585304579789098">"Зърнистост"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Черно-бяло"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Черно-бяло"</string>
-    <string name="highlight" msgid="3902653944386623972">"Просветляване"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Ломо"</string>
-    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
-    <string name="posterize" msgid="4139212359561383385">"Плакат"</string>
-    <string name="redeye" msgid="4958448806369928239">"Червени очи"</string>
-    <string name="rotate" msgid="6607597269792373083">"Завъртане"</string>
-    <string name="saturation" msgid="8621322012271169931">"Насищане"</string>
-    <string name="sepia" msgid="7978093531824705601">"Сепия"</string>
-    <string name="shadow" msgid="8235188588101973090">"Засенчване"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Отчетливост"</string>
-    <string name="straighten" msgid="5217801513491493491">"Изправяне"</string>
-    <string name="temperature" msgid="1607987938521534517">"Топлина"</string>
-    <string name="tint" msgid="154435943863418434">"Нюансиране"</string>
-    <string name="vignette" msgid="7648125924662648282">"Винетиране"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Плъзнете маркерите, за да подрежете снимката"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Рисувайте директно върху снимката"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Плъзнете с пръст върху снимката, за да я обърнете"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Докоснете „черв. очи“ за премахването им"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Плъзнете с пръст върху снимката, за да я завъртите"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Плъзнете с пръст върху снимката, за да я изправите"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ефекти на експониране"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Цветови ефекти"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Художествени ефекти"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Коригиране"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Отмяна"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Възстановяване"</string>
-</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 2268ccd..2e9a257 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Завъртане надясно"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Елементът не можа да бъде намерен."</string>
     <string name="edit" msgid="1502273844748580847">"Редактиране"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Обикн. редактиране"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Заявките за кеширане се обработват"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кешира се..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Подрязване"</string>
     <string name="trim_action" msgid="703098114452883524">"Отрязване"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Спиране"</string>
     <string name="set_as" msgid="3636764710790507868">"Задаване като"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Клипът не може да се спре."</string>
     <string name="video_err" msgid="7003051631792271009">"Видеоклипът не може да се възпроизведе."</string>
     <string name="group_by_location" msgid="316641628989023253">"По местоположение"</string>
     <string name="group_by_time" msgid="9046168567717963573">"По време"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Авт."</string>
     <string name="flash_on" msgid="7891556231891837284">"Със светкавица"</string>
     <string name="flash_off" msgid="1445443413822680010">"Без светкавица"</string>
+    <string name="unknown" msgid="3506693015896912952">"Неизвестно"</string>
     <string name="ffx_original" msgid="372686331501281474">"Оригинал"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Ретро"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Моментно фото"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Няма налично външно хранилище"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Изглед „Филмова лента“"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Табличен изглед"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Изглед на цял екран"</string>
     <string name="trimming" msgid="9122385768369143997">"Отрязва се"</string>
+    <string name="muting" msgid="5094925919589915324">"Спира се"</string>
     <string name="please_wait" msgid="7296066089146487366">"Моля, изчакайте"</string>
-    <string name="save_into" msgid="4960537214388766062">"Отрязаният видеоклип се запазва в албума:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Видеоклипът се запазва в/ъв „<xliff:g id="ALBUM_NAME">%1$s</xliff:g>“…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Не може да се отреже: Целевият видеоклип е твърде кратък"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Панорамата се изобразява"</string>
     <string name="save" msgid="613976532235060516">"Запазване"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Съдържанието се сканира..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Сканирани са %1$d елемента"</item>
+    <item quantity="one" msgid="4340019444460561648">"Сканиран е %1$d елемент"</item>
+    <item quantity="other" msgid="3138021473860555499">"Сканирани са %1$d елемента"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Сортира се..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Сканирането завърши"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Импортира се..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Няма съдържание, налично за импортиране на това устройство."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Няма свързано устройство, поддържащо MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Грешка в камерата"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Не може да се осъществи връзка с камерата."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Камерата е деактивирана заради правилата за сигурност."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Камера"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Видеокамера"</string>
+    <string name="wait" msgid="8600187532323801552">"Моля, изчакайте…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Свържете USB хранилището, преди да използвате камерата."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Поставете SD карта, преди да използвате камерата."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB хранилището се подготвя..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD картата се подготвя..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Няма достъп до USB хранилището."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Няма достъп до SD картата."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ОТКАЗ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ГОТОВО"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Запис на цайтрафера"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Избор на камера"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Задна"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Предна"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Място за съхранение"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"МЕСТОПОЛОЖЕНИЕ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Таймер за обратното отброяване"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 секунда"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d секунди"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Писукане при обратното отбр."</string>
+    <string name="setting_off" msgid="4480039384202951946">"Изключено"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Включено"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Качество на видеоклип"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Високо"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Ниско"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Цайтрафер"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Настройки на камера"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Настройки на видеокамера"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Размер на снимка"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 мегапиксела"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 мегапиксела"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 мегапиксел"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Фокусен режим"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Автоматичен"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Безкрайност"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Макро"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"АВТОМАТИЧНО"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"БЕЗКРАЙНОСТ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"МАКРО"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Светкавица"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"РЕЖИМ НА СВЕТКАВИЦАТА"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Автоматичен"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Включена"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Изкл."</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"АВТОМАТИЧНА СВЕТКАВИЦА"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"СВЕТКАВИЦАТА Е ВКЛЮЧЕНА"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"СВЕТКАВИЦАТА Е ИЗКЛЮЧЕНА"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Баланс на бялото"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"БАЛАНС НА БЯЛОТО"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Автоматичен"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Изкуствена светлина"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Дневна светлина"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Флуоресцентна светлина"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Облачно"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"АВТОМАТИЧНО"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ИЗКУСТВЕНА СВЕТЛИНА"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ДНЕВНА СВЕТЛИНА"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ФЛУОРЕСЦЕНТНА СВЕТЛИНА"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ОБЛАЧНО"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Сценичен режим"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Автоматичен"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Действие"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Нощ"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Залез"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Празненство"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"БЕЗ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ДЕЙСТВИЕ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ПРЕЗ НОЩТА"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ЗАЛЕЗ"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ПАРТИ"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ТАЙМЕР ЗА ОБРАТНО ОТБРОЯВАНЕ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ТАЙМЕРЪТ Е ИЗКЛЮЧЕН"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 СЕКУНДА"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 СЕКУНДИ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 СЕКУНДИ"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 СЕКУНДИ"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Не може да се избира в сценичен режим."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Eкспониране"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ЕКСПОНИРАНЕ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ПРЕДНА КАМЕРА"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ЗАДНА КАМЕРА"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Мястото на USB хранилището ви привършва. Променете настройките за качество или изтрийте някои изображения или други файлове."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Мястото на SD картата ви привършва. Променете настройките за качество или изтрийте някои изображения или други файлове."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Достигнат е макс. размер."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Твърде бързо"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Панорамата се подготвя"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Панорамата не можа да бъде запазена."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Панорама"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Панорамата се заснема"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Изчаква се предишната панорама"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Запазва се..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Панорамата се изобразява"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Докоснете за фокусиране."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Ефекти"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Без ефекти"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Разкривяване"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Големи очи"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Голяма уста"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Малка уста"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Голям нос"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Малки очи"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"В космоса"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Залез"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Видеоклипът ви"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Оставете устройството си."\n"Отдръпнете се от зрителното му поле за момент."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Докоснете, за да направите снимка, докато записвате."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Видеозаписът започна."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Видеозаписът спря."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Моментните снимки в клиповете са деакт. при вкл. спец. ефекти."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Изчистване на ефектите"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"СМЕШНИ ЛИЦА"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ФОН"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Бутон на затвора"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Бутон за меню"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Най-скорошна снимка"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Превключване между предната и задната камера"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Избор между камера, видеокамера или панорама"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Още контроли за настройки"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Затваряне на контролите за настройки"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Контрола за промяна на мащаба"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Намаляване на %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Увеличаване на %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Квадратче за отметка за %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Превключване към фотоапарат"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Превключване към видео"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Превключване към панорама"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Превключване към нова панорама"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Анулиране на прегледа"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Прегледът приключи"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Преглед на презаснетия елемент"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Пускане на видеоклипа"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Поставяне на видеоклипа на пауза"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Презареждане на видеоклипа"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Времева лента на видеоплейъра"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ВКЛЮЧЕНО"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ИЗКЛЮЧЕНО"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Изкл."</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 секунда"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 минути"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 час"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 часа"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 часа"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"секунди"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"минути"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"часа"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Готово"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Задаване на интервал от време"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Функцията за цайтрафер е изключена. Включете я, за да зададете интервал от време."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Таймерът за обратното отброяване е изключен. Включете го, за да отброява, преди да направите снимка."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Задаване на продължителността в секунди"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Обратно отброяване до правенето на снимка"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Да се запомнят ли местоположенията на снимките?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Поставете в снимките и видеоклиповете си маркери с местоположенията, на които са направени."\n\n"Другите приложения могат да осъществяват достъп до тази информация, както и до запазените ви изображения."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Не, благодаря"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Търсене"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Снимки"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Албуми"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ОЩЕ ОПЦИИ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"НАСТРОЙКИ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d снимка"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d снимки"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ca/filtershow_strings.xml b/res/values-ca/filtershow_strings.xml
index 0945162..7da09b9 100644
--- a/res/values-ca/filtershow_strings.xml
+++ b/res/values-ca/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"No es pot carregar la imatge."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"S\'està establint el fons de pantalla"</string>
+    <string name="download_failure" msgid="5923323939788582895">"No s\'ha pogut baixar la foto. La xarxa no està disponible."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Vores"</string>
-    <string name="done" msgid="3112344807927554662">"Fet"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Desfés"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Refés"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostra l\'historial"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Amaga l\'historial"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Mostra estat imatge"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Amaga estat d\'imatge"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostra els efectes aplicats"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Amaga els efectes aplicats"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Configuració"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Hi ha canvis sense desar en aquesta imatge."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vols desar abans de sortir?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Desa i surt"</string>
+    <string name="exit" msgid="242642957038770113">"Surt"</string>
     <string name="history" msgid="455767361472692409">"Historial"</string>
     <string name="reset" msgid="9013181350779592937">"Restableix"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Estat de la imatge actual"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efectes aplicats"</string>
     <string name="compare_original" msgid="8140838959007796977">"Compara"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplica"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Restableix"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Cap"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"S\'ha corregit"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planeta petit"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposició"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidesa"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"To de color"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Punts brillants"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Corbes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ulls vermells"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Dibuixos"</string>
     <string name="straighten" msgid="26025591664983528">"Redreça"</string>
     <string name="crop" msgid="5781263790107850771">"Retalla"</string>
     <string name="rotate" msgid="2796802553793795371">"Gira"</string>
     <string name="mirror" msgid="5482518108154883096">"Reflex"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatiu"</string>
     <string name="none" msgid="6633966646410296520">"Cap"</string>
+    <string name="edge" msgid="7036064886242147551">"Vores"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Reduïda"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Vermell"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verd"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blau"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Mida"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Color"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Línies"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marcador"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Esquitx"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Esborra"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Tria un color personalitzat"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Selecció del color"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Selecció de la mida"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"D\'acord"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultat"</string>
 </resources>
diff --git a/res/values-ca/photoeditor_strings.xml b/res/values-ca/photoeditor_strings.xml
deleted file mode 100644
index f782fd7..0000000
--- a/res/values-ca/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Estudi de fotografia"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"No s\'ha pogut carregar la foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"No s\'ha pogut desar la foto editada"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"S\'ha desat la foto editada a <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vols descartar els canvis no desats?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sí"</string>
-    <string name="save" msgid="5516670392524294967">"DESA"</string>
-    <string name="autofix" msgid="1663414996270538748">"Correcció automàtica"</string>
-    <string name="crop" msgid="7598378507763334041">"Retalla"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Procés creuat"</string>
-    <string name="documentary" msgid="50396326708699797">"Documental"</string>
-    <string name="doodle" msgid="1686409894518940990">"Gargots"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dos tons"</string>
-    <string name="facelift" msgid="6205748523156172637">"Brillantor cara"</string>
-    <string name="facetan" msgid="4412831806626044111">"Pell morena"</string>
-    <string name="filllight" msgid="2644989991700022526">"Retroil·lumin."</string>
-    <string name="fisheye" msgid="6037488646928998921">"Ull de peix"</string>
-    <string name="flip" msgid="2357692401826287480">"Inversió"</string>
-    <string name="grain" msgid="7487585304579789098">"Gra pel·lícula"</string>
-    <string name="grayscale" msgid="7641563843060945228">"B/N"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Blanc i negre"</string>
-    <string name="highlight" msgid="3902653944386623972">"Realçaments"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatiu"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterització"</string>
-    <string name="redeye" msgid="4958448806369928239">"Ulls vermells"</string>
-    <string name="rotate" msgid="6607597269792373083">"Gir"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturació"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sèpia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Ombres"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Nitidesa"</string>
-    <string name="straighten" msgid="5217801513491493491">"Redreçament"</string>
-    <string name="temperature" msgid="1607987938521534517">"Calidesa"</string>
-    <string name="tint" msgid="154435943863418434">"Tint"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinyeta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Arrossega els marcadors per retallar la foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Arrossega el cursor sobre la foto per dibuixar"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Arrossega la foto per invertir-la"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Toca els ulls vermells per eliminar-los"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrossega la foto per girar-la"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrossega la foto per redreçar-la"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efectes d\'exposició"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efectes de color"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efectes artístics"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Repara"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Desfés"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Refés"</string>
-</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 30f05ea..514db19 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Gira a la dreta"</string>
     <string name="no_such_item" msgid="5315144556325243400">"No s\'ha trobat l\'element."</string>
     <string name="edit" msgid="1502273844748580847">"Edita"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edició simple"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"S\'estan processant les sol·licituds de memòria cau"</string>
     <string name="caching_label" msgid="4521059045896269095">"S\'està desant a la memòria cau..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Retalla"</string>
     <string name="trim_action" msgid="703098114452883524">"Retalla"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Silencia"</string>
     <string name="set_as" msgid="3636764710790507868">"Defineix com a"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"No es pot silenciar el vídeo."</string>
     <string name="video_err" msgid="7003051631792271009">"No es pot reproduir el vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Per ubicació"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Per temps"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Automàtic"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flaix disparat"</string>
     <string name="flash_off" msgid="1445443413822680010">"Sense flaix"</string>
+    <string name="unknown" msgid="3506693015896912952">"Desconeguda"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Instantani"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instantània"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Destenyeix"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Blau"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"B/N"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"No hi ha emmagatzematge extern disponible"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Vista de tira de pel·lícula"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Vista de quadrícula"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Visual. pant. compl."</string>
     <string name="trimming" msgid="9122385768369143997">"S\'està retallant"</string>
+    <string name="muting" msgid="5094925919589915324">"S\'està silenciant"</string>
     <string name="please_wait" msgid="7296066089146487366">"Espera"</string>
-    <string name="save_into" msgid="4960537214388766062">"S\'està desant el vídeo retallat a l\'àlbum:"</string>
+    <string name="save_into" msgid="9155488424829609229">"S\'està desant el vídeo a <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"No es pot retallar: el vídeo de destinació és massa curt"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panorama de representació"</string>
     <string name="save" msgid="613976532235060516">"Desa"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"S\'està explorant el contingut..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elements explorats"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d element explorat"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elements explorats"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"S\'està ordenant..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Exploració finalitzada"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"S\'està important..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"No hi ha contingut disponible per importar en aquest dispositiu."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"No hi ha cap dispositiu MTP connectat"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Error de la càmera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"No es pot connectar a la càmera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"La càmera s\'ha desactivat a causa de les polítiques de seguretat."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Càmera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Càmera de vídeo"</string>
+    <string name="wait" msgid="8600187532323801552">"Espereu-vos…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Instal·la l\'emmagatzematge USB abans d\'utilitzar la càmera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Insereix una targeta SD abans d\'utilitzar la càmera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Prep. l\'emmagatzematge USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"S\'està preparant la targeta SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"No s\'ha pogut accedir a l\'emmagatzematge USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"No s\'ha pogut accedir a la targeta SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCEL·LA"</string>
+    <string name="review_ok" msgid="1156261588693116433">"FET"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"S\'està enregistrant lapse de temps"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Tria de la càmera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Enrere"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Frontal"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Emmagatzema la ubicació"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"UBICACIÓ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Temporitzador de compte enrere"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 segon"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d segons"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <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>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Baixa"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Lapse de temps"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Configuració de la càmera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Configuració de la càmera de vídeo"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Mida de la imatge"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 megapíxels (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapíxels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapíxel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Mode d\'enfocament"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automàtic"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinit"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMÀTIC"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINIT"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Mode de flaix"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODE DE FLAIX"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automàtic"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"A"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Desactivat"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLAIX AUTOMÀTIC"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLAIX ACTIVAT"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLAIX DESACTIVAT"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balanç de blancs"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANÇ DE BLANCS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automàtica"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescent"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Llum de dia"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescent"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Ennuvolat"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMÀTIC"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENT"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LLUM DE DIA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENT"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ENNUVOLAT"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Mode d\'escena"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automàtic"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acció"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nocturn"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Posta del sol"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Festa"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"CAP"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACCIÓ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NIT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"POSTA DE SOL"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"TEMPORITZADOR DE COMPTE ENRERE"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORITZADOR DESACTIVAT"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGON"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGONS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGONS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGONS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"No es pot seleccionar en mode d\'escena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposició"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSICIÓ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CÀMERA FRONTAL"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CÀMERA POSTERIOR"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"D\'acord"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"L\'emmagatzematge USB s\'està quedant sense espai. Canvia la configuració de qualitat o bé suprimeix unes quantes imatges o altres fitxers."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"La targeta SD s\'està quedant sense espai. Canvia la configuració de qualitat o suprimeix unes quantes imatges o altres fitxers."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"S\'ha arribat al límit de mida"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Massa ràpid"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"S\'està preparant el panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"No s\'ha pogut desar el panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"S\'està capturant un panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"S\'està esperant la panoràmica anterior"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"S\'està desant..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panorama de representació"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Toca per enfocar."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efectes"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Cap"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Aixafa"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Ulls grans"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Boca gran"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Boca petita"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nas gran"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Ulls petits"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"A l\'espai"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Posta del sol"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"El teu vídeo"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Deixa el dispositiu."\n"Surt un moment de la visualització."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Toca per fer una foto mentre enregistres."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"L\'enregistrament de vídeo ha començat."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"L\'enregistrament de vídeo s\'ha aturat."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Les instantànies del vídeo es desactiven quan hi ha els efectes especials activats."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Esborra efectes"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"GANYOTES"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FONS"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Botó de l\'obturador"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Botó de menú"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto més recent"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Commutador de càmera anterior i posterior"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Càmera, vídeo o selector de panorames"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Més controls de configuració"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Tanca els controls de configuració"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Control de zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Redueix %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Augmenta %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Casella de selecció %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Canvia a foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Canvia a vídeo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Canvia a panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Canvia al panorama nou"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Cancel·la en mode de revisió"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Fet en mode de revisió"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Revisa la foto o el vídeo nou"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Reprodueix el vídeo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Posa en pausa el vídeo"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Torna a carregar el vídeo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra temporal del reproductor de vídeo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ACTIVAT"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DESACTIVAT"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Desactivat"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segon"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 segons"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuts"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 hores"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 hores"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"segons"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuts"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"hores"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Fet"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Definició d\'interval de temps"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"La funció de lapse de temps està desactivada. Activa-la per definir l\'interval de temps."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"El temporitzador del compte enrere està desactivat. Activa el compte enrere abans de fer una foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Configuració de la durada en segons"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Compte enrere per fer una foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Vols que es recordin les ubicacions de les fotos?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta les teves fotos i els teus vídeos amb les ubicacions des de les quals es fan."\n\n"Altres aplicacions podran accedir a aquesta informació juntament amb les imatges desades."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"No, gràcies"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Càmera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Cerca"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Àlbums"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MÉS OPCIONS"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"CONFIGURACIÓ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-cs/filtershow_strings.xml b/res/values-cs/filtershow_strings.xml
index dbdffe9..b5a2c18 100644
--- a/res/values-cs/filtershow_strings.xml
+++ b/res/values-cs/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Obrázek nelze načíst!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Nastavování tapety"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotografii se nepodařilo stáhnout. Síť je nedostupná."</string>
     <string name="original" msgid="3524493791230430897">"Původní"</string>
     <string name="borders" msgid="2067345080568684614">"Okraje"</string>
-    <string name="done" msgid="3112344807927554662">"Hotovo"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Vrátit zpět"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Opakovat"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Zobrazit historii"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Skrýt historii"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Ukázat stav obrázku"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Skrýt stav obrázku"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Ukázat použité efekty"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Skrýt použité efekty"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Nastavení"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Obrázek obsahuje neuložené změny."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Chcete před ukončením uložit změny?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Uložit a ukončit"</string>
+    <string name="exit" msgid="242642957038770113">"Ukončit"</string>
     <string name="history" msgid="455767361472692409">"Historie"</string>
     <string name="reset" msgid="9013181350779592937">"Obnovit"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Aktuální stav obrázku"</string>
+    <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
     <string name="compare_original" msgid="8140838959007796977">"Porovnat"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Použít"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Obnovit"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Žádný"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Pevné"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Malá planeta"</string>
     <string name="exposure" msgid="6526397045949374905">"Expozice"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ostrost"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. barva"</string>
     <string name="hue" msgid="6231252147971086030">"Odstín"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Stíny"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Světlá místa"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Křivky"</string>
     <string name="vignette" msgid="934721068851885390">"Viněta"</string>
     <string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Kresby"</string>
     <string name="straighten" msgid="26025591664983528">"Vyrovnat"</string>
     <string name="crop" msgid="5781263790107850771">"Oříznutí"</string>
     <string name="rotate" msgid="2796802553793795371">"Otočit"</string>
     <string name="mirror" msgid="5482518108154883096">"Zrcadlit"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Žádný"</string>
+    <string name="edge" msgid="7036064886242147551">"Hrany"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Zmenšit"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Červená"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zelená"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Modrá"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Styl"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Velikost"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Barva"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Čáry"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Zvýrazňovač"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Cákance"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Vymazat"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Zvolit vlastní barvu"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Vyberte barvu"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Vyberte velikost"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Původní"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Výsledek"</string>
 </resources>
diff --git a/res/values-cs/photoeditor_strings.xml b/res/values-cs/photoeditor_strings.xml
deleted file mode 100644
index e67b254..0000000
--- a/res/values-cs/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotografii nelze načíst"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Upravená fotografie nelze uložit"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Upravená fotografie uložena do složky <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Zahodit neuložené změny?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ano"</string>
-    <string name="save" msgid="5516670392524294967">"ULOŽIT"</string>
-    <string name="autofix" msgid="1663414996270538748">"Automat. oprava"</string>
-    <string name="crop" msgid="7598378507763334041">"Oříznout"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Křížové zpracování"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
-    <string name="doodle" msgid="1686409894518940990">"Kreslení"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duplex"</string>
-    <string name="facelift" msgid="6205748523156172637">"Zářivý obličej"</string>
-    <string name="facetan" msgid="4412831806626044111">"Opálený obličej"</string>
-    <string name="filllight" msgid="2644989991700022526">"Projasnění"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Rybí oko"</string>
-    <string name="flip" msgid="2357692401826287480">"Převrácení"</string>
-    <string name="grain" msgid="7487585304579789098">"Zrnitý film"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Černobílé"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Černobílé"</string>
-    <string name="highlight" msgid="3902653944386623972">"Odlesky"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizace"</string>
-    <string name="redeye" msgid="4958448806369928239">"Červené oči"</string>
-    <string name="rotate" msgid="6607597269792373083">"Otočení"</string>
-    <string name="saturation" msgid="8621322012271169931">"Sytost"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sépiové barvy"</string>
-    <string name="shadow" msgid="8235188588101973090">"Stíny"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Zaostření"</string>
-    <string name="straighten" msgid="5217801513491493491">"Vyrovnání"</string>
-    <string name="temperature" msgid="1607987938521534517">"Teplo"</string>
-    <string name="tint" msgid="154435943863418434">"Tónování"</string>
-    <string name="vignette" msgid="7648125924662648282">"Medailonek"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Táhnutím značek fotografii oříznete"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii můžete kreslit prstem"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Táhnutím prstem fotografii převrátíte"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Červené oči odstraníte klepnutím na oči"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Táhnutím prstem fotografii otočíte"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Táhnutím prstem fotografii narovnáte"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efekty expozice"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Barevné efekty"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Umělecké efekty"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Korekce"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Vrátit zpět"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Znovu"</string>
-</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 9203dbe..ea46d50 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -45,7 +45,7 @@
     <string name="select_item" msgid="2816923896202086390">"Vybrat položku"</string>
     <string name="select_album" msgid="1557063764849434077">"Vybrat album"</string>
     <string name="select_group" msgid="6744208543323307114">"Vybrat skupinu"</string>
-    <string name="set_image" msgid="2331476809308010401">"Nastavit fotografii jako"</string>
+    <string name="set_image" msgid="2331476809308010401">"Nastavit fotku jako"</string>
     <string name="set_wallpaper" msgid="8491121226190175017">"Nastavit tapetu"</string>
     <string name="wallpaper" msgid="140165383777262070">"Nastavování tapety..."</string>
     <string name="camera_setas_wallpaper" msgid="797463183863414289">"Tapeta"</string>
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Otočit doprava"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Položku nelze najít."</string>
     <string name="edit" msgid="1502273844748580847">"Upravit"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Jednoduchá úprava"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Zpracování požadavků na uložení do mezipaměti"</string>
     <string name="caching_label" msgid="4521059045896269095">"Ukládání do mezipaměti..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Oříznout"</string>
     <string name="trim_action" msgid="703098114452883524">"Oříznout"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Ztlumit"</string>
     <string name="set_as" msgid="3636764710790507868">"Nastavit jako"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Video nelze ztlumit."</string>
     <string name="video_err" msgid="7003051631792271009">"Video nelze přehrát."</string>
     <string name="group_by_location" msgid="316641628989023253">"Podle místa"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Podle času"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Autom."</string>
     <string name="flash_on" msgid="7891556231891837284">"S bleskem"</string>
     <string name="flash_off" msgid="1445443413822680010">"Bez blesku"</string>
+    <string name="unknown" msgid="3506693015896912952">"Neznámé"</string>
     <string name="ffx_original" msgid="372686331501281474">"Původní"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Klasika"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instantní"</string>
@@ -192,11 +196,244 @@
     <string name="no_external_storage_title" msgid="2408933644249734569">"Žádné úložiště"</string>
     <string name="no_external_storage" msgid="95726173164068417">"Žádné externí úložiště není k dispozici"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Zobrazení filmového pásu"</string>
-    <string name="switch_photo_grid" msgid="3681299459107925725">"Zobrazení v mřížce"</string>
+    <string name="switch_photo_grid" msgid="3681299459107925725">"Mřížkové zobrazení"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Celá obrazovka"</string>
     <string name="trimming" msgid="9122385768369143997">"Zkracování"</string>
+    <string name="muting" msgid="5094925919589915324">"Probíhá ztlumení"</string>
     <string name="please_wait" msgid="7296066089146487366">"Čekejte prosím"</string>
-    <string name="save_into" msgid="4960537214388766062">"Ukládání oříznutého videa do alba:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Ukládání videa do alba <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Zkrácení nelze provést: výsledné video je příliš krátké"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Vykreslování panoramatu"</string>
     <string name="save" msgid="613976532235060516">"Uložit"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skenování obsahu..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"počet skenovaných položek: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"počet skenovaných položek: %1$d"</item>
+    <item quantity="other" msgid="3138021473860555499">"počet skenovaných položek: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Řazení..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skenování dokončeno"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Probíhá import..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Není k dispozici žádný obsah pro import do tohoto zařízení."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Není připojeno žádné zařízení MTP."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Chyba fotoaparátu"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nelze se připojit k fotoaparátu."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Fotoaparát byl z důvodu zásad zabezpečení deaktivován."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotoaparát"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Čekejte prosím..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Před použitím fotoaparátu připojte úložiště USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Před použitím fotoaparátu prosím vložte SD kartu."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Příprava úložiště USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Příprava karty SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nelze získat přístup k úložišti USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nelze získat přístup ke kartě SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ZRUŠIT"</string>
+    <string name="review_ok" msgid="1156261588693116433">"HOTOVO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Časosběrný záznam"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Zvolit fotoaparát"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Zadní"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Přední"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Úložiště"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"POLOHA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Časovač odpočítávání"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 s"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d s"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Zvuk při odpočtu"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Vypnuto"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Zapnuto"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kvalita videa"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Vysoká"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Nízká"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Časosběr"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Nastavení fotoaparátu"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Nastavení videokamery"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Velikost fotografií"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixelů"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Mpx (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Režim zaostření"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Nekonečno"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"NEKONEČNO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Režim blesku"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"REŽIM BLESKU"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automaticky"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Zapnout"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Vypnout"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATICKÝ BLESK"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLESK ZAPNUTÝ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLESK VYPNUTÝ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Vyvážení bílé"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"VYVÁŽENÍ BÍLÉ"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automaticky"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Žárovka"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Denní světlo"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Zářivka"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Zataženo"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ŽÁROVKA"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DENNÍ SVĚTLO"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ZÁŘIVKA"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ZATAŽENO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scénický režim"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automaticky"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Akce"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noc"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Západ slunce"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Párty"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ŽÁDNÉ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AKCE"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOC"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ZÁPAD SLUNCE"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"VEČÍREK"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ČASOVAČ ODPOČÍTÁVÁNÍ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ČASOVAČ VYPNUT"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDA"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDY"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUND"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUND"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Tuto možnost nelze ve scénickém režimu vybrat."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Expozice"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOZICE"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PŘEDNÍ FOTOAPARÁT"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ZADNÍ FOTOAPARÁT"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"V úložišti USB je málo místa. Změňte nastavení kvality nebo smažte některé fotografie či jiné soubory."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Na kartě SD je málo místa. Změňte nastavení kvality nebo smažte některé obrázky či jiné soubory."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Bylo dosaženo limitu velikosti."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Moc rychle"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Příprava panoramatu"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panoramatickou fotku nelze uložit."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Snímání panoramatu"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Čeká se na předchozí panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Ukládání..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Vykreslování panoramatu"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Dotykem zaostříte."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efekty"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Žádný"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Zmáčknout"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Velké oči"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Velká ústa"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Malá ústa"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Velký nos"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Malé oči"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Ve vesmíru"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Západ slunce"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Vaše video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Položte zařízení."\n"Vystupte na chvíli ze zorného pole."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Dotykem v průběhu nahrávání pořídíte snímek."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Záznam videa byl zahájen."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Záznam videa byl zastaven."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Jsou-li zapnuty zvláštní efekty, jsou snímky videa zakázány."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Vymazat efekty"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"BLÁZNIVÉ TVÁŘE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"POZADÍ"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Tlačítko závěrky"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Tlačítko Menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Poslední fotografie"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Přepínač mezi předním a zadním fotoaparátem"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Přepínač mezi panoramatickým režimem a režimy fotoaparátu a videokamery"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Další ovládací prvky nastavení"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Zavřít ovládací prvky nastavení"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Ovládání přiblížení/oddálení"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Snížit %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Zvýšit %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s zaškrtávací políčko"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Přepnout na fotoaparát"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Přepnout na video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Přepnout do panoramatického režimu"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Přepnout do nového panoramatického režimu"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Zrušit"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Hotovo"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Pořídit další"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Přehrát video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pozastavit video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Znovu načíst video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Časová lišta přehrávače videa"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ZAPNUTO"</string>
+    <string name="capital_off" msgid="7231052688467970897">"VYPNUTO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Vypnuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hodina"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 hodin"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 hodin"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 hodin"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 hodin"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 hodin"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 hodin"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekundy"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuty"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"hodiny"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Hotovo"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Nastavit časový interval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funkce časosběr je vypnutá. Zapněte ji a nastavte časový interval."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Časovač odpočítávání je vypnutý. Chcete-li odpočítávat do aktivace spouště fotoaparátu, zapněte jej."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Nastavit dobu v sekundách"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Odpočítávání spouště fotoaparátu"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Pamatovat, kde byly fotky pořízeny?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Přidejte do fotek a videí označení míst, kde jste je pořídili."\n\n"Ostatní aplikace budou mít k těmto informacím přístup společně s přístupem k uloženým obrázkům."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ne, děkuji"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ano"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotoaparát"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Hledat"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotky"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Alba"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"DALŠÍ MOŽNOSTI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"NASTAVENÍ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotka"</item>
+    <item quantity="other" msgid="3813306834113858135">"Fotky: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-da/filtershow_strings.xml b/res/values-da/filtershow_strings.xml
index b49b0ea..9fe1463 100644
--- a/res/values-da/filtershow_strings.xml
+++ b/res/values-da/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Billedet kan ikke indlæses."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Angiver baggrund"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Kunne ikke downloade billede. Netværk utilgængeligt."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Rammer"</string>
-    <string name="done" msgid="3112344807927554662">"Udfør"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Fortryd"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Annuller fortryd"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Vis historik"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Skjul historik"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Vis billedtilstand"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Skjul billedtilstand"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Vis anvendte effekter"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Skjul anvendte effekter"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Indstillinger"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Der er ændringer på dette billede, som ikke er gemt."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vil du gemme, før du afslutter?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Gem og afslut"</string>
+    <string name="exit" msgid="242642957038770113">"Afslut"</string>
     <string name="history" msgid="455767361472692409">"Historik"</string>
     <string name="reset" msgid="9013181350779592937">"Nulstil"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Nuværende billedtilstand"</string>
+    <string name="imageState" msgid="8632586742752891968">"Anvendte effekter"</string>
     <string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Anvend"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Nulstil"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ingen"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fastlåst"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Eksponering"</string>
     <string name="sharpness" msgid="6463103068318055412">"Skarphed"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Automatisk farve"</string>
     <string name="hue" msgid="6231252147971086030">"Nuance"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Fremhævninger"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
     <string name="vignette" msgid="934721068851885390">"Vignet"</string>
     <string name="redeye" msgid="4508883127049472069">"Røde øjne"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Tegn"</string>
     <string name="straighten" msgid="26025591664983528">"Ret op"</string>
     <string name="crop" msgid="5781263790107850771">"Beskær"</string>
     <string name="rotate" msgid="2796802553793795371">"Roter"</string>
     <string name="mirror" msgid="5482518108154883096">"Spejl"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Ingen"</string>
+    <string name="edge" msgid="7036064886242147551">"Kanter"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Reducer"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rød"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Grøn"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blå"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Størrelse"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Farve"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linjer"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Tusch"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Stænk"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Ryd"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Vælg tilpasset farve"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Vælg farve"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Vælg størrelse"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultat"</string>
 </resources>
diff --git a/res/values-da/photoeditor_strings.xml b/res/values-da/photoeditor_strings.xml
deleted file mode 100644
index d2dcb0b..0000000
--- a/res/values-da/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotostudie"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotoet kunne ikke indlæses"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Det redigerede foto kunne ikke gemmes"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Det redigerede foto er gemt i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kassér ændringer, som ikke er gemt?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"GEM"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autojustering"</string>
-    <string name="crop" msgid="7598378507763334041">"Beskær"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Krydsfremkald"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentar"</string>
-    <string name="doodle" msgid="1686409894518940990">"Krusedulle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Tofarvet"</string>
-    <string name="facelift" msgid="6205748523156172637">"Retouchering"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Oplys mørke"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fiskeøje"</string>
-    <string name="flip" msgid="2357692401826287480">"Vend"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmkorn"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Sort-hvid"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Sort og hvid"</string>
-    <string name="highlight" msgid="3902653944386623972">"Fremhævning"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterisering"</string>
-    <string name="redeye" msgid="4958448806369928239">"Røde øjne"</string>
-    <string name="rotate" msgid="6607597269792373083">"Roter"</string>
-    <string name="saturation" msgid="8621322012271169931">"Mætning"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Skygger"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Gør skarpere"</string>
-    <string name="straighten" msgid="5217801513491493491">"Ret op"</string>
-    <string name="temperature" msgid="1607987938521534517">"Varme"</string>
-    <string name="tint" msgid="154435943863418434">"Nuance"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignet"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Træk markører for at beskære billedet"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på billedet for at lave kruseduller"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Træk billedet for at vende det"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Tryk på røde øjne for at fjerne dem"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Træk billedet for at rotere det"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Træk i billedet for at rette det op"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Eksponeringseffekter"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Farveeffekter"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Kunstneriske effekter"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Rediger"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Fortryd"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Annuller fortryd"</string>
-</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 213d93a..9fde3de 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Roter til højre"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Elementet blev ikke fundet."</string>
     <string name="edit" msgid="1502273844748580847">"Rediger"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Simpel redigering"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Håndterer anmodninger om cachelagring"</string>
     <string name="caching_label" msgid="4521059045896269095">"Cachelagrer..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskær"</string>
     <string name="trim_action" msgid="703098114452883524">"Beskær"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Slå lyd fra"</string>
     <string name="set_as" msgid="3636764710790507868">"Indstil som"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Videolyden kan ikke slås fra."</string>
     <string name="video_err" msgid="7003051631792271009">"Videoen kan ikke afspilles."</string>
     <string name="group_by_location" msgid="316641628989023253">"Efter placering"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Efter tid"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Blitz affyret"</string>
     <string name="flash_off" msgid="1445443413822680010">"Ingen blitz"</string>
+    <string name="unknown" msgid="3506693015896912952">"Ukendt"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,241 @@
     <string name="no_external_storage" msgid="95726173164068417">"Intet tilgængeligt eksternt lager"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstrip-visning"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Gittervisning"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Se i fuld skærm"</string>
     <string name="trimming" msgid="9122385768369143997">"Beskæring"</string>
+    <string name="muting" msgid="5094925919589915324">"Slår lyden fra"</string>
     <string name="please_wait" msgid="7296066089146487366">"Vent"</string>
-    <string name="save_into" msgid="4960537214388766062">"Gemmer beskåret video i album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Videoen gemmes i <xliff:g id="ALBUM_NAME">%1$s</xliff:g>..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"Der kan ikke beskæres – målvideoen er for kort"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panoramabilledet gengives"</string>
     <string name="save" msgid="613976532235060516">"Gem"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Indhold scannes..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elementer er scannet"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d element er scannet"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elementer er scannet"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorterer..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scanning er fuldført"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importerer..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Denne enhed indeholder intet indhold, der kan importeres."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Der er ingen MTP-enhed tilsluttet"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerafejl"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Der kan ikke oprettes forbindelse til kameraet."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kameraet er deaktiveret på grund af sikkerhedspolitikker."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Vent..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Isæt USB-lager, før du tager kameraet i brug."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Indsæt et SD-kort, før kameraet tages i brug."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Forbereder USB-lager…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Forbereder SD-kort ..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Der kunne ikke fås adgang til USB-lagring."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Der kunne ikke fås adgang til SD-kortet."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ANNULLER"</string>
+    <string name="review_ok" msgid="1156261588693116433">"FÆRDIG"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Optagelse af tidsforløb"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Vælg kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Bagest"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Forrest"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Gem placering"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"PLACERING"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Nedtællingsur"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for pref_camera_timer_entry:other (6455381617076792481) -->
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Beep under nedtællingen"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Fra"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Til"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videokvalitet"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Høj"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Lav"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Tidsforløb"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Indstillinger for kamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Indstillinger for videokamera"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Billedstørrelse"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M pixels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M pixels (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokustilstand"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatisk"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Uendelighed"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATISK"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"UENDELIGT"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Blitztilstand"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"BLITZTILSTAND"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatisk"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Til"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Fra"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATISK BLITZ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLITZ TIL"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLITZ FRA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Hvidbalance"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"HVIDBALANCE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatisk"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Blødt lys"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dagslys"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Hårdt lys"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Overskyet"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATISK"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"BLØDT LYS"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAGSLYS"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"HÅRDT LYS"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"OVERSKYET"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scenetilstand"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatisk"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Bevægelse"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nat"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Solnedgang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fest"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"INGEN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"HANDLING"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NAT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SOLNEDGANG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FEST"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"NEDTÆLLINGSUR"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"SLÅ NEDTÆLLINGSUR FRA"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUND"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDER"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Kan ikke vælges i motivtilstand."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Eksponering"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EKSPONERING"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FRONTKAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"BAGSIDEKAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Der er snart ikke mere plads i USB-lager. Rediger indstillingerne for kvalitet, eller slet nogle billeder eller andre filer."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Der er snart ikke mere plads på dit SD-kort. Rediger indstillingerne for kvalitet, eller slet nogle billeder eller andre filer."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Størrelsesgrænse er nået."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"For hurtig"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Forbereder panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorama kunne ikke gemmes."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Optagelse af panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Venter på et tidligere panoramabillede"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Gemmer..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panoramabilledet gengives"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Tryk for at fokusere."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effekter"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ingen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Klem"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Store øjne"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Stor mund"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Lille mund"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Stor næse"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Små øjne"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"I rummet"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Solnedgang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Din video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Læg din enhed ned."\n"Træd ud af syne et øjeblik."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Tryk for at tage et foto, mens du optager."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Videooptagelsen er startet."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Videooptagelsen er stoppet."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Videoøjebliksbilleder deaktiveres, når specialeffekter er tændt."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Ryd effekter"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"FJOLLEDE ANSIGTER"</string>
+    <string name="effect_background" msgid="6579360207378171022">"BAGGRUND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Lukkerknap"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menu-knap"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Det seneste foto"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Skift mellem frontkameraet og bagsidekameraet"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Valg af kamera, video eller panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Flere indstillingskontroller"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Luk indstillingskontroller"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoomkontrol"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Formindsk %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Forøg %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Afkrydsningsfeltet for %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Skift til foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Skift til video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Skift til panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Skift til nyt panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Annullering af gennemgang"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Gennemgang fuldført"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Lav foto/video til anmeldelsen om"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Afspil video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Sæt video på pause"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Genindlæs video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Tidsbjælke til videoafspiller"</string>
+    <string name="capital_on" msgid="5491353494964003567">"TIL"</string>
+    <string name="capital_off" msgid="7231052688467970897">"FRA"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Fra"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 time"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 timer"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunder"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutter"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"timer"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Udført"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Angiv tidsinterval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funktionen Tidsforløb er deaktiveret. Aktivér den for at indstille tidsintervallet."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Nedtællingsur er slukket. Slå den til at tælle ned, før du tager et billede."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Indstil varigheden i sekunder"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Tælle ned for at tage et foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Skal fotosteder huskes?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Tag dine fotos og videoer med de placeringer, hvor de blev taget."\n\n"Andre apps kan få adgang til disse oplysninger sammen med dine gemte billeder."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nej tak"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Søg"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albummer"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"FLERE VALGMULIGHEDER"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"INDSTILLINGER"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d billede"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d billeder"</item>
+  </plurals>
 </resources>
diff --git a/res/values-de/filtershow_strings.xml b/res/values-de/filtershow_strings.xml
index d9856c9..8018ca1 100644
--- a/res/values-de/filtershow_strings.xml
+++ b/res/values-de/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Bild kann nicht geladen werden."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Hintergrund wird festgelegt..."</string>
+    <string name="download_failure" msgid="5923323939788582895">"Foto konnte nicht heruntergeladen werden. Netzwerk ist nicht verfügbar."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Rahmen"</string>
-    <string name="done" msgid="3112344807927554662">"Fertig"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Rückgängig machen"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Wiederholen"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Verlauf anzeigen"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Verlauf ausblenden"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Bildstatus anzeigen"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Bildstatus ausblenden"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Angewendete Effekte anzeigen"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Angewendete Effekte ausblenden"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Einstellungen"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Dieses Bild weist nicht gespeicherte Änderungen auf."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Möchten Sie vor dem Schließen speichern?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Speichern und schließen"</string>
+    <string name="exit" msgid="242642957038770113">"Schließen"</string>
     <string name="history" msgid="455767361472692409">"Verlauf"</string>
     <string name="reset" msgid="9013181350779592937">"Zurücksetzen"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Aktueller Bildstatus"</string>
+    <string name="imageState" msgid="8632586742752891968">"Angewendete Effekte"</string>
     <string name="compare_original" msgid="8140838959007796977">"Vergleichen"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Übernehmen"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Zurücksetzen"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Kein Effekt"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fest"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Belichtung"</string>
     <string name="sharpness" msgid="6463103068318055412">"Schärfe"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. Farbe"</string>
     <string name="hue" msgid="6231252147971086030">"Farbton"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Schatten"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Highlights"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurven"</string>
     <string name="vignette" msgid="934721068851885390">"Vignettierung"</string>
     <string name="redeye" msgid="4508883127049472069">"Rote Augen"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Zeichnen"</string>
     <string name="straighten" msgid="26025591664983528">"Ausrichten"</string>
     <string name="crop" msgid="5781263790107850771">"Zuschneiden"</string>
     <string name="rotate" msgid="2796802553793795371">"Drehen"</string>
     <string name="mirror" msgid="5482518108154883096">"Spiegeln"</string>
-    <string name="none" msgid="6633966646410296520">"Keines"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
+    <string name="none" msgid="6633966646410296520">"Kein Filter"</string>
+    <string name="edge" msgid="7036064886242147551">"Kanten"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Verkleinern"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rot"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Grün"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blau"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Größe"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Farbe"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linien"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Filzstift"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spritzer"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Löschen"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Benutzerdefinierte Farbe wählen"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Farbe auswählen"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Größe auswählen"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Ergebnis"</string>
 </resources>
diff --git a/res/values-de/photoeditor_strings.xml b/res/values-de/photoeditor_strings.xml
deleted file mode 100644
index 9c30da5..0000000
--- a/res/values-de/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Foto konnte nicht geladen werden."</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Speichern des bearbeiteten Fotos nicht möglich"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Das bearbeitete Foto wurde in <xliff:g id="FOLDER_NAME">%s</xliff:g> gespeichert."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Nicht gespeicherte Änderungen verwerfen?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"Speichern"</string>
-    <string name="autofix" msgid="1663414996270538748">"Auto-Fix"</string>
-    <string name="crop" msgid="7598378507763334041">"Zuschneiden"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Cross-Entwickl."</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentation"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duoton"</string>
-    <string name="facelift" msgid="6205748523156172637">"Weichzeichnen"</string>
-    <string name="facetan" msgid="4412831806626044111">"Bräunen"</string>
-    <string name="filllight" msgid="2644989991700022526">"Aufhellen"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fischauge"</string>
-    <string name="flip" msgid="2357692401826287480">"Spiegeln"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmkörnung"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Schwarz-Weiß"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Schwarz-Weiß"</string>
-    <string name="highlight" msgid="3902653944386623972">"Highlights"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Tontrennung"</string>
-    <string name="redeye" msgid="4958448806369928239">"Rote Augen"</string>
-    <string name="rotate" msgid="6607597269792373083">"Drehen"</string>
-    <string name="saturation" msgid="8621322012271169931">"Sättigung"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Schatten"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Scharf stellen"</string>
-    <string name="straighten" msgid="5217801513491493491">"Ausrichten"</string>
-    <string name="temperature" msgid="1607987938521534517">"Wärme"</string>
-    <string name="tint" msgid="154435943863418434">"Färbung"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignettierung"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Zum Zuschneiden des Fotos Markierungen ziehen"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Zum Malen auf dem Foto malen"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Foto ziehen, um es umzudrehen"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Zur Korrektur auf rote Augen tippen"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Foto ziehen, um es zu drehen"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Foto ziehen, um es zu strecken"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Belichtungseffekte"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Farbeffekte"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Künstlerische Effekte"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Ausbessern"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Rückgängig machen"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Wiederholen"</string>
-</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index d29949c..264659a 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Nach rechts drehen"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Bild wurde nicht gefunden."</string>
     <string name="edit" msgid="1502273844748580847">"Bearbeiten"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Einfache Bearbeitung"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Caching-Anfragen werden verarbeitet."</string>
     <string name="caching_label" msgid="4521059045896269095">"Caching läuft..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Zuschneiden"</string>
     <string name="trim_action" msgid="703098114452883524">"Zuschneiden"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Ton aus"</string>
     <string name="set_as" msgid="3636764710790507868">"Festlegen als"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Ton aus nicht möglich"</string>
     <string name="video_err" msgid="7003051631792271009">"Video kann nicht wiedergegeben werden."</string>
     <string name="group_by_location" msgid="316641628989023253">"Nach Standort"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Nach Zeit"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Automatisch"</string>
     <string name="flash_on" msgid="7891556231891837284">"Blitz ausgelöst"</string>
     <string name="flash_off" msgid="1445443413822680010">"Ohne Blitz"</string>
+    <string name="unknown" msgid="3506693015896912952">"Unbekannt"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Sofort"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Bleach"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Blau"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"Schwarz-Weiß"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Kein externer Speicher verfügbar"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstreifen-Ansicht"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Rasteransicht"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Vollbildansicht"</string>
     <string name="trimming" msgid="9122385768369143997">"Wird zugeschnitten"</string>
+    <string name="muting" msgid="5094925919589915324">"Ton wird ausgeschaltet..."</string>
     <string name="please_wait" msgid="7296066089146487366">"Bitte warten"</string>
-    <string name="save_into" msgid="4960537214388766062">"Zugeschnittenes Video wird im Album gespeichert:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Video wird in <xliff:g id="ALBUM_NAME">%1$s</xliff:g> gespeichert…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Zuschneiden nicht möglich: Ziel-Video zu kurz"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panorama wird gerendert..."</string>
     <string name="save" msgid="613976532235060516">"Speichern"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Inhalt wird gescannt..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d Elemente gescannt"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d Element gescannt"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d Elemente gescannt"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Wird sortiert..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scan abgeschlossen"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Wird importiert..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Es sind keine Inhalte zum Importieren auf dieses Gerät vorhanden."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Es ist kein MTP-Gerät angeschlossen."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerafehler"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Keine Verbindung zur Kamera möglich"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Die Kamera wurde aufgrund von Sicherheitsrichtlinien deaktiviert."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Camcorder"</string>
+    <string name="wait" msgid="8600187532323801552">"Bitte warten..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Stellen Sie vor Verwendung der Kamera einen USB-Speicher bereit."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Legen Sie vor Verwendung der Kamera eine SD-Karte ein."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB-Speicher wird vorbereitet"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD-Karte wird vorbereitet..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Kein Zugriff auf USB-Speicher möglich"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Kein Zugriff auf SD-Karte möglich"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"Abbrechen"</string>
+    <string name="review_ok" msgid="1156261588693116433">"Fertig"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Zeitrafferaufnahme"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Kamera wählen"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Rückseite"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Vorderseite"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Ort speichern"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"Standort"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Countdown-Timer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 Sekunde"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d Sekunden"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Piepton während Countdown"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Aus"</string>
+    <string name="setting_on" msgid="8602246224465348901">"An"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videoqualität"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Hoch"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Niedrig"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Zeitraffer"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kameraeinstellungen"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Camcordereinstellungen"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Bildgröße"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 Megapixel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 MP"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 MP"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 MP (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 MP"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 MP"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokussierungsmodus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatisch"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Unendlich"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"Automatisch"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"Unendlich"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"Makro"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Blitzmodus"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"Blitzmodus"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatisch"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"An"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Aus"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"Automatischer Blitz"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"Blitz an"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"Blitz aus"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Weißabgleich"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"Weißabgleich"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatisch"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Glühlampenlicht"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Tageslicht"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Neonlicht"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Bewölkt"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"Automatisch"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"Glühlampenlicht"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"Tageslicht"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"Neonlicht"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"Bewölkt"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Szenenmodus"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatisch"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Action"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nachtaufnahme"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Sonnenuntergang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Party"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"Kein"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"Aktion"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"Nachts"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"Sonnenuntergang"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"Party"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"Countdown-Timer"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"Timer deaktiviert"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 Sekunde"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 Sekunden"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 Sekunden"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 Sekunden"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Diese Einstellung kann im Szenenmodus nicht ausgewählt werden."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Belichtung"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"Belichtung"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR; High Dynamic Range"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"Frontkamera"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"Kamera auf der Rückseite"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Ihr USB-Speicher bietet nicht mehr genug Speicherplatz. Ändern Sie die Qualitätseinstellung oder löschen Sie Bilder oder andere Dateien."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Auf Ihrer SD-Karte ist nicht mehr genügend Speicherplatz vorhanden. Ändern Sie die Qualitätseinstellung oder löschen Sie Bilder oder andere Dateien."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Maximale Größe erreicht"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Zu schnell"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panorama wird vorbereitet..."</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorama konnte nicht gespeichert werden."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panorama wird aufgenommen..."</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Warten auf vorheriges Panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Speichern..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panorama wird gerendert..."</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Zum Fokussieren tippen"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effekte"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Effekt löschen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Quetschen"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Große Augen"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Großer Mund"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Kleiner Mund"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Große Nase"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Kleine Augen"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Im All"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Sonnenuntergang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Mein Video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Legen Sie Ihr Gerät ab."\n"Treten Sie für einen kurzen Moment aus dem Aufnahmebereich."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Zum Erstellen eines Fotos während der Aufnahme tippen"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Videoaufzeichnung wurde gestartet."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Videoaufzeichnung wurde beendet."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Video-Schnappschuss ist bei Spezialeffekten deaktiviert."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Effekte löschen"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"Lustige Gesichter"</string>
+    <string name="effect_background" msgid="6579360207378171022">"Hintergrund"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Auslöseknopf"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menüschaltfläche"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Neuestes Foto"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Zwischen Kamera auf der Vorder- und Rückseite wechseln"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Auswahl: Kamera, Video oder Panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Weitere Einstellungen"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Einstellungen schließen"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoom-Steuerung"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Verkleinern %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Vergrößern %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Kontrollkästchen \"%1$s\""</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"In Kamera-Modus wechseln"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"In Video-Modus wechseln"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"In Panorama-Modus wechseln"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Zum neuen Panorama wechseln"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Überprüfung abbrechen"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Überprüfung abgeschlossen"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Überprüfen – erneut aufnehmen"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Video ansehen"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Video anhalten"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Video erneut laden"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Zeitleiste des Videoplayers"</string>
+    <string name="capital_on" msgid="5491353494964003567">"An"</string>
+    <string name="capital_off" msgid="7231052688467970897">"Aus"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Aus"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 Sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 Sekunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 Minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 Minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 Stunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 Stunden"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 Stunden"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"Sekunden"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"Minuten"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"Stunden"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Fertig"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Zeitintervall festlegen"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Die Zeitraffer-Funktion ist ausgeschaltet. Schalten Sie sie ein, um das Zeitintervall festzulegen."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Der Countdown-Timer ist deaktiviert. Aktivieren Sie ihn, damit vor der Aufnahme eines Bildes ein Countdown erfolgt."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Dauer in Sekunden festlegen"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Countdown für Fotoaufnahme"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Aufnahmeorte für Fotos speichern?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Taggen Sie Ihre Fotos und Videos mit den Standorten, an denen sie aufgenommen wurden."\n\n"Andere Apps können auf diese Informationen und Ihre gespeicherten Bilder zugreifen."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nein"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Suchen"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Alben"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"Weitere Optionen"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"Einstellungen"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d Foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d Fotos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-el/filtershow_strings.xml b/res/values-el/filtershow_strings.xml
index a2b53a1..bfbd589 100644
--- a/res/values-el/filtershow_strings.xml
+++ b/res/values-el/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Δεν είναι δυνατή η φόρτωση της εικόνας!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Ορισμός ταπετσαρίας…"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Δεν ήταν δυνατή η λήψη φωτογραφιών. Το δίκτυο δεν είναι διαθέσιμο."</string>
     <string name="original" msgid="3524493791230430897">"Αρχική"</string>
     <string name="borders" msgid="2067345080568684614">"Σύνορα"</string>
-    <string name="done" msgid="3112344807927554662">"Ολοκληρώθηκε"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Αναίρεση"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Επανάληψη"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Εμφάνιση ιστορικού"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Απόκρυψη ιστορικού"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Εμφ.κατάστ.εικόνας"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Απόκρ.κατάστ.εικόνας"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Εμφάνιση εφαρμοσμένων εφέ"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Απόκρυψη εφαρμοσμένων εφέ"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Ρυθμίσεις"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Υπάρχουν μη αποθηκευμένες αλλαγές σε αυτήν την εικόνα."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Θέλετε να γίνει αποθήκευση πριν από την έξοδο;"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Αποθήκευση και έξοδος"</string>
+    <string name="exit" msgid="242642957038770113">"Έξοδος"</string>
     <string name="history" msgid="455767361472692409">"Ιστορικό"</string>
     <string name="reset" msgid="9013181350779592937">"Επαναφορά"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Κατάσταση τρέχουσας εικόνας"</string>
+    <string name="imageState" msgid="8632586742752891968">"Εφαρμοσμένα εφέ"</string>
     <string name="compare_original" msgid="8140838959007796977">"Σύγκριση"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Εφαρμογή"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Επαναφορά"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Κανένα"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Σταθερός"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Έκθεση"</string>
     <string name="sharpness" msgid="6463103068318055412">"Οξύτητα"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Αυτόματο χρώμα"</string>
     <string name="hue" msgid="6231252147971086030">"Απόχρωση"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Σκιές"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Φωτεινά σημεία"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Καμπύλες"</string>
     <string name="vignette" msgid="934721068851885390">"Βινιετάρισμα"</string>
     <string name="redeye" msgid="4508883127049472069">"Κόκκινα μάτια"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Σχεδίαση"</string>
     <string name="straighten" msgid="26025591664983528">"Ευθυγράμμιση"</string>
     <string name="crop" msgid="5781263790107850771">"Περικοπή"</string>
     <string name="rotate" msgid="2796802553793795371">"Εναλλαγή"</string>
     <string name="mirror" msgid="5482518108154883096">"Κατοπτρισμός"</string>
+    <string name="negative" msgid="6998313764388022201">"Αρνητικό"</string>
     <string name="none" msgid="6633966646410296520">"Κανένα"</string>
+    <string name="edge" msgid="7036064886242147551">"Άκρες"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Σμίκρυνση"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Κόκκινο"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Πράσινο"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Μπλε"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Στυλ"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Μέγεθος"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Χρώμα"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Γραμμές"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Μαρκαδόρος"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Πιτσιλιές"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Διαγραφή"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Επιλογή προσαρμ.χρώματος"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Επιλογή χρώματος"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Επιλογή μεγέθους"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"ΟΚ"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Αρχική"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Αποτέλεσμα"</string>
 </resources>
diff --git a/res/values-el/photoeditor_strings.xml b/res/values-el/photoeditor_strings.xml
deleted file mode 100644
index e0b9239..0000000
--- a/res/values-el/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Αδύνατη η φόρτωση της φωτογραφίας"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Αδύνατη  η αποθήκευση της επεξ. φωτογρ."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Η επεξ. φωτογραφία αποθηκ. στον φάκελο <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Απόρριψη μη αποθηκευμένων αλλαγών;"</string>
-    <string name="yes" msgid="5402582493291792293">"Ναι"</string>
-    <string name="save" msgid="5516670392524294967">"ΑΠΟΘΗΚΕΥΣΗ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Αυτ.επιδ."</string>
-    <string name="crop" msgid="7598378507763334041">"Περικοπή"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Cross Process"</string>
-    <string name="documentary" msgid="50396326708699797">"Ντοκιμαντέρ"</string>
-    <string name="doodle" msgid="1686409894518940990">"Σκετσάκι"</string>
-    <string name="duotone" msgid="8145893940788467106">"Δύο τόνοι"</string>
-    <string name="facelift" msgid="6205748523156172637">"Πρόσωπο με λάμψη"</string>
-    <string name="facetan" msgid="4412831806626044111">"Μαυρισμένο πρόσωπο"</string>
-    <string name="filllight" msgid="2644989991700022526">"Γέμισμα με φως"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Αναστροφή"</string>
-    <string name="grain" msgid="7487585304579789098">"Κόκκος φιλμ"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Ασπρόμ."</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Μαύρο και άσπρο"</string>
-    <string name="highlight" msgid="3902653944386623972">"Επισημάνσεις"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Αρνητικό"</string>
-    <string name="posterize" msgid="4139212359561383385">"Ποστεροποίηση"</string>
-    <string name="redeye" msgid="4958448806369928239">"Κόκκινα μάτια"</string>
-    <string name="rotate" msgid="6607597269792373083">"Περιστροφή"</string>
-    <string name="saturation" msgid="8621322012271169931">"Κορεσμός"</string>
-    <string name="sepia" msgid="7978093531824705601">"Σέπια"</string>
-    <string name="shadow" msgid="8235188588101973090">"Σκιές"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Όξυνση"</string>
-    <string name="straighten" msgid="5217801513491493491">"Ευθυγράμμιση"</string>
-    <string name="temperature" msgid="1607987938521534517">"Θερμότ."</string>
-    <string name="tint" msgid="154435943863418434">"Απόχρωση"</string>
-    <string name="vignette" msgid="7648125924662648282">"Βινιετάρισμα"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Σύρετε τους δείκτες για την περικοπή της φωτογραφίας"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Σχεδιάστε στη φωτογραφία για να δημιουργήσετε ένα σκετσάκι"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Σύρετε τη φωτογραφία για να την αναστρέψετε"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Πατήστε στα κόκκινα μάτια για διόρθωση"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Σύρετε τη φωτογραφία για να την περιστρέψετε"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Σύρετε τη φωτογραφία για να την ευθυγραμμίσετε"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Εφέ έκθεσης"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Εφέ χρωμάτων"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Καλλιτεχνικά εφέ"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Επιδιόρθωση"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Αναίρεση"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Επανάληψη"</string>
-</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 40eb558..26a5af5 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Δεξιά περιστροφή"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Δεν ήταν δυνατή η εύρεση."</string>
     <string name="edit" msgid="1502273844748580847">"Επεξεργασία"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Απλή επεξεργασία"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Επεξεργασία αιτημάτων προσωρινής αποθήκευσης"</string>
     <string name="caching_label" msgid="4521059045896269095">"Προσωρ. αποθ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Περικοπή"</string>
     <string name="trim_action" msgid="703098114452883524">"Περικοπή"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Σίγαση"</string>
     <string name="set_as" msgid="3636764710790507868">"Ορισμός ως"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Αδύνατη η σίγαση του βίντεο."</string>
     <string name="video_err" msgid="7003051631792271009">"Δεν είναι δυνατή η αναπαραγωγή βίντεο."</string>
     <string name="group_by_location" msgid="316641628989023253">"Κατά τοποθεσία"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Κατά ώρα"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Αυτόματο"</string>
     <string name="flash_on" msgid="7891556231891837284">"Το φλας άναψε"</string>
     <string name="flash_off" msgid="1445443413822680010">"Όχι φλας"</string>
+    <string name="unknown" msgid="3506693015896912952">"Άγνωστο"</string>
     <string name="ffx_original" msgid="372686331501281474">"Αρχική"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Εποχής"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Άμεσα"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Δεν υπάρχει διαθέσιμος εξωτερικός χώρος αποθήκευσης"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Προβολή σε φιλμ"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Προβολή πλέγματος"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Πλήρης οθόνη"</string>
     <string name="trimming" msgid="9122385768369143997">"Περικοπή"</string>
+    <string name="muting" msgid="5094925919589915324">"Σίγαση"</string>
     <string name="please_wait" msgid="7296066089146487366">"Περιμένετε"</string>
-    <string name="save_into" msgid="4960537214388766062">"Αποθήκευση βίντεο που έχει περικοπεί στο λεύκωμα :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Αποθήκευση βίντεο στο άλμπουμ <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Δεν είναι δυνατή η περικοπή : το βίντεο-στόχος είναι πολύ σύντομο"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Απόδοση πανοράματος"</string>
     <string name="save" msgid="613976532235060516">"Αποθήκευση"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Σάρωση περιεχομένου…"</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d στοιχεία έχουν σαρωθεί"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d στοιχείο έχει σαρωθεί"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d στοιχεία έχουν σαρωθεί"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Ταξινόμηση…"</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Η σάρωση έχει ολοκληρωθεί"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Εισαγωγή…"</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Δεν υπάρχει διαθέσιμο περιεχόμενο για εισαγωγή σε αυτή τη συσκευή."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Δεν υπάρχει συνδεδεμένη συσκευή MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Σφάλμα φωτογραφικής μηχανής"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Δεν είναι δυνατή η σύνδεση με τη φωτογραφική μηχανή."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Η φωτογραφική μηχανή έχει απενεργοποιηθεί λόγω των πολιτικών ασφαλείας."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Φωτογ.μηχανή"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Βιντεοκάμ."</string>
+    <string name="wait" msgid="8600187532323801552">"Περιμένετε..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Προσαρτήστε τον χώρο αποθήκευσης USB προτού κάνετε χρήση της φωτογραφικής μηχανής."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Εισαγάγετε κάρτα SD πριν χρησιμοποιήσετε τη φωτογραφική μηχανή."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Προετοιμασία χώρου αποθ. USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Προετοιμασία κάρτας SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Δεν ήταν δυνατή η πρόσβαση στο χώρο αποθήκευσης USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Δεν ήταν δυνατή η πρόσβαση στην κάρτα SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ΑΚΥΡΩΣΗ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ΤΕΛΟΣ"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Εγγραφή παρέλευσης χρόνου"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Επιλογή φωτ. μηχανής"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Πίσω"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Μπροστά"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Αποθήκευση τοποθεσίας"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"ΤΟΠΟΘΕΣΙΑ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Χρονόμετρο αντίστροφης μέτρησης"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 δευτερόλεπτο"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d δευτερόλεπτα"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Ήχος κατά τη διάρκεια της αντίστροφης μέτρησης"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Απενεργοποιημ."</string>
+    <string name="setting_on" msgid="8602246224465348901">"Ενεργοποιημ."</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Ποιότητα βίντεο"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Υψηλή"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Χαμηλή"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Παρέλευση χρόνου"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Ρυθμίσεις φωτογραφικής μηχανής"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Ρυθμίσεις βιντεοκάμερας"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Μέγεθος εικόνας"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M  εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M εικον.(16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M εικονοστ."</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Λειτουργία εστίασης"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Αυτόματο"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Άπειρο"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Απόσταση"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"ΑΥΤΟΜΑΤΟ"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"ΑΠΕΙΡΟ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"ΜΑΚΡΟΦΩΤΟΓΡΑΦΙΑ"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Λειτουργία Flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"ΛΕΙΤΟΥΡΓΙΑ ΦΛΑΣ"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Αυτόματο"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Ενεργοποιημένο"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"ΑΠΕΝΕΡΓΟΠΟΙΗΜΕΝΟ"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"ΑΥΤΟΜΑΤΟ ΦΛΑΣ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"ΕΝΕΡΓΟΠΟΙΗΣΗ ΦΛΑΣ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ ΦΛΑΣ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Ισορροπία λευκού"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"ΙΣΟΡΡΟΠΙΑ ΛΕΥΚΟΥ"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Αυτόματο"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Λαμπτήρας πυρακτώσεως"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Φως ημέρας"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Λαμπτήρας φθορισμού"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Συννεφιά"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"ΑΥΤΟΜΑΤΟ"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ΛΑΜΠΤΗΡΑΣ ΠΥΡΑΚΤΩΣΕΩΣ"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ΦΩΣ ΗΜΕΡΑΣ"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ΛΑΜΠΤΗΡΑΣ ΦΘΟΡΙΣΜΟΥ"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ΣΥΝΝΕΦΙΑ"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Λειτουργία σκηνής"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Αυτόματο"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Ενέργεια"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Νύχτα"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Ηλιοβασίλεμα"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Πάρτι"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ΚΑΜΙΑ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ΔΡΑΣΗ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ΝΥΧΤΑ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ΗΛΙΟΒΑΣΙΛΕΜΑ"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ΠΑΡΤΙ"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ΧΡΟΝΟΜΕΤΡΟ ΑΝΤΙΣΤΡΟΦΗΣ ΜΕΤΡΗΣΗΣ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ΑΝΕΝΕΡΓΟ ΧΡΟΝΟΜΕΤΡΟ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 ΔΕΥΤΕΡΟΛΕΠΤΟ"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 ΔΕΥΤΕΡΟΛΕΠΤΑ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 ΔΕΥΤΕΡΟΛΕΠΤΑ"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 ΔΕΥΤΕΡΟΛΕΠΤΑ"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Δεν υπάρχει δυνατότητα επιλογής στη λειτουργία σκηνής."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Έκθεση"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ΕΚΘΕΣΗ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"Υψηλό δυναμικό εύρος (HDR)"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ΜΠΡΟΣΤΙΝΗ ΚΑΜΕΡΑ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ΠΙΣΩ ΚΑΜΕΡΑ"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Ο διαθέσιμος χώρος USB είναι ελάχιστος. Αλλάξτε τη ρύθμιση της ποιότητας ή διαγράψτε κάποιες εικόνες ή άλλα αρχεία."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Ο διαθέσιμος χώρος στην κάρτα SD είναι ελάχιστος. Αλλάξτε τη ρύθμιση ποιότητας ή διαγράψτε κάποιες εικόνες ή άλλα αρχεία."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Συμπληρώθηκε το όριο μεγέθους."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Πολύ γρήγορα"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Προετοιμασία πανοραμικής εικ."</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Δεν ήταν δυνατή η αποθήκευση του πανοράματος."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Πανόραμα"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Λήψη πανοράματος"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Αναμονή για προηγ. πανοραμική εικόνα"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Aποθήκευση..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Απόδοση πανοράματος"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Πατήστε για εστίαση."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Εφέ"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Κανένα"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Πιέστε"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Μεγάλα μάτια"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Μεγάλο στόμα"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Μικρό στόμα"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Μεγάλη μύτη"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Μικρά μάτια"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Στο διάστημα"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Δύση ηλίου"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Το βίντεό σας"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Αφήστε τη συσκευή σας κάτω"\n"Απομακρυνθείτε για λίγο από την εμβέλειά της."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Αγγίξτε για λήψη φωτογραφίας κατά την εγγραφή."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Έχει ξεκινήσει η εγγραφή βίντεο."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Έχει διακοπεί η εγγραφή βίντεο."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Το στιγμιότυπο οθόνης βίντεο απενεργοποιείται με ενεργά τα εφέ."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Διαγραφή εφέ"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ΑΣΤΕΙΑ ΠΡΟΣΩΠΑ"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ΦΟΝΤΟ"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Κουμπί κλείστρου"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Κουμπί μενού"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Πιο πρόσφατη φωτογραφία"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Διακόπτης εμπρός και πίσω φωτογραφικής μηχανής"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Επιλογέας φωτογραφικής μηχανής, βίντεο ή πανοράματος"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Περισσότερα στοιχεία ελέγχου ρυθμίσεων"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Κλείσιμο στοιχείων ελέγχου ρυθμίσεων"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Έλεγχος εστίασης"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Μείωση %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Αύξηση %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s πλαίσιο επιλογής"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Μετάβαση σε φωτογραφία"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Μετάβαση σε λειτουργία βίντεο"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Μετάβαση σε πανόραμα"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Μετάβαση σε νέο πανόραμα"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Ακύρωση αναθεώρησης"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Ολοκλήρωση αναθεώρησης"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Έλεγχος νέας λήψης"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Αναπαραγωγή βίντεο"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Παύση βίντεο"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Επανάληψη φόρτωσης βίντεο"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Γραμμή χρόνου προγράμματος αναπαραγωγής βίντεο"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ΕΝΕΡΓΟΠΟΙΗΣΗ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ΑΠΕΝΕΡΓΟΠΟΙΗΣΗ"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Απενεργοποιημένη"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 δευτερόλεπτο"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 δευτερόλεπτο"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 δευτερόλεπτο"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 δευτερόλεπτα"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 λεπτό"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 λεπτό"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 λεπτό"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 λεπτά"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ώρα"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ώρα"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ώρα"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ώρες"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ώρες"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"δευτερόλεπτα"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"λεπτά"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ώρες"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Τέλος"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Ορισμός χρονικού διαστήματος"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Η λειτουργία παρέλευσης χρόνου έχει απενεργοποιηθεί. Ενεργοποιήστε την για να ορίσετε το χρονικό διάστημα."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Το χρονόμετρο αντίστροφης μέτρησης είναι απενεργοποιημένο. Ενεργοποιήστε το για να ξεκινήσει η αντίστροφη μέτρηση πριν τη λήψη φωτογραφίας."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Ορισμός διάρκειας σε δευτερόλεπτα"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Αντίστροφη μέτρηση για λήψη φωτογραφίας"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Απομνημόνευση τοποθεσίας φωτογραφιών;"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Προσθέστε ετικέτες στις φωτογραφίες και τα βίντεό σας με την τοποθεσία στην οποία έχουν τραβηχτεί."\n\n"Οι άλλες εφαρμογές μπορούν να αποκτήσουν πρόσβαση σε αυτές τις πληροφορίες μαζί με τις αποθηκευμένες σας εικόνες."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Όχι ευχαριστώ"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ναι"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Κάμερα"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Αναζήτηση"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Φωτογραφίες"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Λεύκωμα"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ΠΕΡΙΣΣΟΤΕΡΕΣ ΕΠΙΛΟΓΕΣ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"ΡΥΘΜΙΣΕΙΣ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d φωτογραφία"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d φωτογραφ."</item>
+  </plurals>
 </resources>
diff --git a/res/values-en-rGB/filtershow_strings.xml b/res/values-en-rGB/filtershow_strings.xml
index 80bb701..19c4ac2 100644
--- a/res/values-en-rGB/filtershow_strings.xml
+++ b/res/values-en-rGB/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Cannot load the image!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Setting wallpaper"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Could not download photo. Network unavailable."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Borders"</string>
-    <string name="done" msgid="3112344807927554662">"Done"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Undo"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Redo"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Show history"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Hide history"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Show Image State"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Hide Image State"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Show Applied Effects"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Hide Applied Effects"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Settings"</string>
+    <string name="unsaved" msgid="8704442449002374375">"There are unsaved changes to this image."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Do you want to save before exiting?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Save and Exit"</string>
+    <string name="exit" msgid="242642957038770113">"Exit"</string>
     <string name="history" msgid="455767361472692409">"History"</string>
     <string name="reset" msgid="9013181350779592937">"Reset"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Current Image State"</string>
+    <string name="imageState" msgid="8632586742752891968">"Applied Effects"</string>
     <string name="compare_original" msgid="8140838959007796977">"Compare"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Apply"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Reset"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"None"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fixed"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposure"</string>
     <string name="sharpness" msgid="6463103068318055412">"Sharpness"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolour"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Shadows"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Highlights"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curves"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Draw"</string>
     <string name="straighten" msgid="26025591664983528">"Straighten"</string>
     <string name="crop" msgid="5781263790107850771">"Crop"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotate"</string>
     <string name="mirror" msgid="5482518108154883096">"Mirror"</string>
+    <string name="negative" msgid="6998313764388022201">"Negative"</string>
     <string name="none" msgid="6633966646410296520">"None"</string>
+    <string name="edge" msgid="7036064886242147551">"Edges"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Downsample"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Red"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Green"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blue"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Style"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Size"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Colour"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Lines"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spatter"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Clear"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Choose custom colour"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Select Colour"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Select Size"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Result"</string>
 </resources>
diff --git a/res/values-en-rGB/photoeditor_strings.xml b/res/values-en-rGB/photoeditor_strings.xml
deleted file mode 100644
index bc8d411..0000000
--- a/res/values-en-rGB/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Couldn\'t load the photo"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Couldn\'t save edited photo"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Edited photo saved to <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Discard unsaved changes?"</string>
-    <string name="yes" msgid="5402582493291792293">"Yes"</string>
-    <string name="save" msgid="5516670392524294967">"SAVE"</string>
-    <string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
-    <string name="crop" msgid="7598378507763334041">"Crop"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Cross-Process"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentary"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Fill Light"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Flip"</string>
-    <string name="grain" msgid="7487585304579789098">"Film Grain"</string>
-    <string name="grayscale" msgid="7641563843060945228">"B&amp;W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Black and white"</string>
-    <string name="highlight" msgid="3902653944386623972">"Highlights"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negative"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterise"</string>
-    <string name="redeye" msgid="4958448806369928239">"Red Eye"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotate"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Shadows"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Sharpen"</string>
-    <string name="straighten" msgid="5217801513491493491">"Straighten"</string>
-    <string name="temperature" msgid="1607987938521534517">"Warmth"</string>
-    <string name="tint" msgid="154435943863418434">"Tint"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Drag markers to crop photo"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Draw on photo to doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Drag photo to flip it"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Tap on red eyes to remove them"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Drag photo to rotate it"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Drag photo to straighten it"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Exposure effects"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Colour effects"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Artistic effects"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Fix up"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Undo"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Redo"</string>
-</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 09a59c5..6225326 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rotate right"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Couldn\'t find item."</string>
     <string name="edit" msgid="1502273844748580847">"Edit"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Simple Edit"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Process Caching Requests"</string>
     <string name="caching_label" msgid="4521059045896269095">"Caching..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Crop"</string>
     <string name="trim_action" msgid="703098114452883524">"Trim"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Mute"</string>
     <string name="set_as" msgid="3636764710790507868">"Set as"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Can\'t mute video."</string>
     <string name="video_err" msgid="7003051631792271009">"Cannot play video"</string>
     <string name="group_by_location" msgid="316641628989023253">"By location"</string>
     <string name="group_by_time" msgid="9046168567717963573">"By time"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash fired"</string>
     <string name="flash_off" msgid="1445443413822680010">"No flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Unknown"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"No external storage available"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstrip view"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Grid view"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Fullscreen view"</string>
     <string name="trimming" msgid="9122385768369143997">"Trimming"</string>
+    <string name="muting" msgid="5094925919589915324">"Muting"</string>
     <string name="please_wait" msgid="7296066089146487366">"Please wait"</string>
-    <string name="save_into" msgid="4960537214388766062">"Saving trimmed video into album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Saving video to <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Cannot trim: target video is too short"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Rendering panorama"</string>
     <string name="save" msgid="613976532235060516">"Save"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Scanning content..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d items scanned"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item scanned"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d items scanned"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorting..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scanning done"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importing..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"There is no content available for importing on this device."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"There is no MTP device connected"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Camera error"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Can\'t connect to the camera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Camera has been disabled because of security policies."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Camera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Camcorder"</string>
+    <string name="wait" msgid="8600187532323801552">"Please wait…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Please mount USB storage before using the camera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Insert an SD card before using the camera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparing USB storage…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparing SD card…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Couldn\'t access USB storage."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Couldn\'t access SD card."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCEL"</string>
+    <string name="review_ok" msgid="1156261588693116433">"DONE"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Time lapse recording"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Choose camera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Back"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Front"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Store location"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCATION"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Countdown timer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 second"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d seconds"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Beep during countdown"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Off"</string>
+    <string name="setting_on" msgid="8602246224465348901">"On"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Video quality"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"High"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Low"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Time lapse"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Camera settings"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Camcorder settings"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Picture size"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M pixels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 M pixels (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Focus mode"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinity"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITY"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flash mode"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"FLASH MODE"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Auto"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"On"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Off"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ON"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH OFF"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"White balance"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"WHITE BALANCE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Auto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescent"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Daylight"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescent"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Cloudy"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENT"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAYLIGHT"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENT"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"CLOUDY"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scene mode"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Auto"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Action"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Night"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Sunset"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Party"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NONE"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACTION"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NIGHT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SUNSET"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PARTY"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"COUNTDOWN TIMER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TIMER OFF"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SECOND"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SECONDS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SECONDS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SECONDS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Not selectable in scene mode."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposure"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSURE"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FRONT CAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"BACK CAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Your USB storage is running out of space. Change the quality setting or delete some images or other files."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Your SD card is running out of space. Change the quality setting or delete some images or other files."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Size limit reached."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Too fast"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Preparing panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Couldn\'t save panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Capturing panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Waiting for previous panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Saving…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Rendering panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Touch to focus."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effects"</string>
+    <string name="effect_none" msgid="3601545724573307541">"None"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Squeeze"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Big eyes"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Big mouth"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Small mouth"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Big nose"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Small eyes"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"In space"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Sunset"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Your video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Set your device down."\n"Move out of view for a moment."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Touch to take photo while recording."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video recording has started."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video recording has stopped."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Video snapshot is disabled when special effects are on."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Clear effects"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"SILLY FACES"</string>
+    <string name="effect_background" msgid="6579360207378171022">"BACKGROUND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Shutter button"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menu button"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Most recent photo"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Front and back camera switch"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Camera, video or panorama selector"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"More settings controls"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Close settings controls"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoom control"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Decrease %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Increase %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s tick box"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Switch to photo"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Switch to video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Switch to panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Switch to new panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Review cancel"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Review done"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Review retake"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Play video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pause video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Reload video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Video player time bar"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ON"</string>
+    <string name="capital_off" msgid="7231052688467970897">"OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Off"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 second"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 seconds"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0–5 Minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hour"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 hour"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 hours"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 hours"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"seconds"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutes"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"hours"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Done"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Set Time Interval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Time lapse feature is off. Turn it on to set time interval."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Countdown timer is off. Turn it on to count down before taking a picture."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Set duration in seconds"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Counting down to take a photo"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Remember photo locations?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Tag your photos and videos with the locations where they are taken."\n\n"Other apps can access this information along with your saved images."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"No thanks"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Yes"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Search"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Photos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"More options"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"SETTINGS"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d photo"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d photos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-es-rUS/filtershow_strings.xml b/res/values-es-rUS/filtershow_strings.xml
index df5fb95..903b80b 100644
--- a/res/values-es-rUS/filtershow_strings.xml
+++ b/res/values-es-rUS/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"No se puede cargar la imagen."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Estableciendo fondo de pantalla..."</string>
+    <string name="download_failure" msgid="5923323939788582895">"No se pudo descargar la foto. La red no está disponible."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Bordes"</string>
-    <string name="done" msgid="3112344807927554662">"Listo"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Deshacer"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Rehacer"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostrar historial"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ocultar historial"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Mostrar estado imag."</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ocultar estado imag."</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostrar efectos aplicados"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ocultar efectos aplicados"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Configuración"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Hay cambios sin guardar en esta imagen."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"¿Quieres guardar los cambios antes de salir?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Guardar y salir"</string>
+    <string name="exit" msgid="242642957038770113">"Salir"</string>
     <string name="history" msgid="455767361472692409">"Historial"</string>
     <string name="reset" msgid="9013181350779592937">"Restablecer"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Estado actual de la imagen"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ninguno"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fijo"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Pequeño planeta"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposición"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidez"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Color autom."</string>
     <string name="hue" msgid="6231252147971086030">"Tono"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Zonas brillant."</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Viñeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Dibujar"</string>
     <string name="straighten" msgid="26025591664983528">"Enderezar"</string>
     <string name="crop" msgid="5781263790107850771">"Recortar"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotar"</string>
     <string name="mirror" msgid="5482518108154883096">"Espejo"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativo"</string>
     <string name="none" msgid="6633966646410296520">"Ninguno"</string>
+    <string name="edge" msgid="7036064886242147551">"Bordes"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Reducir"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RVA"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rojo"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Azul"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estilo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Tamaño"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Color"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Líneas"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marcador"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Salpicadura"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Borrar"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Elegir un color personalizado"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Seleccionar color"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Seleccionar tamaño"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Aceptar"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultado"</string>
 </resources>
diff --git a/res/values-es-rUS/photoeditor_strings.xml b/res/values-es-rUS/photoeditor_strings.xml
deleted file mode 100644
index 7f95d5f..0000000
--- a/res/values-es-rUS/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Estudio de fotografía"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"No se puede cargar la foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"No se pudo guardar la foto editada"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"La foto editada se guardó en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"¿Descartar cambios sin guardar?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sí"</string>
-    <string name="save" msgid="5516670392524294967">"GUARDAR"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autocorrección"</string>
-    <string name="crop" msgid="7598378507763334041">"Recortar"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proceso cruzado"</string>
-    <string name="documentary" msgid="50396326708699797">"Documental"</string>
-    <string name="doodle" msgid="1686409894518940990">"Garabatear"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dos tonos"</string>
-    <string name="facelift" msgid="6205748523156172637">"Brillo facial"</string>
-    <string name="facetan" msgid="4412831806626044111">"Bronceado facial"</string>
-    <string name="filllight" msgid="2644989991700022526">"Aumentar brillo"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Ojo de pez"</string>
-    <string name="flip" msgid="2357692401826287480">"Cambiar"</string>
-    <string name="grain" msgid="7487585304579789098">"Grano de la película"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Blanco y negro"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Blanco y negro"</string>
-    <string name="highlight" msgid="3902653944386623972">"Destacadas"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomografía"</string>
-    <string name="negative" msgid="1985508917342811252">"negativo"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizar"</string>
-    <string name="redeye" msgid="4958448806369928239">"Ojos rojos"</string>
-    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturación"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Mejorar nitidez"</string>
-    <string name="straighten" msgid="5217801513491493491">"Enderezar"</string>
-    <string name="temperature" msgid="1607987938521534517">"Calor"</string>
-    <string name="tint" msgid="154435943863418434">"Matiz"</string>
-    <string name="vignette" msgid="7648125924662648282">"Viñeta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Arrastrar los marcadores para recortar la foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darla vuelta"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Elimina los ojos rojos tocando el icono"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para hacerla girar"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ajustes de la exposición"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Ajustes del color"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efectos artísticos"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Arreglar"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Deshacer"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Rehacer"</string>
-</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index f5c8aab..4d1ad60 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rotar hacia la derecha"</string>
     <string name="no_such_item" msgid="5315144556325243400">"No se pudo encontrar el elemento."</string>
     <string name="edit" msgid="1502273844748580847">"Editar"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edición simple"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Procesando solicitudes de almacenamiento en caché"</string>
     <string name="caching_label" msgid="4521059045896269095">"Alm en caché..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
     <string name="trim_action" msgid="703098114452883524">"Recortar"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Silenciar"</string>
     <string name="set_as" msgid="3636764710790507868">"Establecer como"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"No se puede silenciar video"</string>
     <string name="video_err" msgid="7003051631792271009">"No se puede reproducir el video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Por ubicación"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Por fecha"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automático"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash activado"</string>
     <string name="flash_off" msgid="1445443413822680010">"Sin flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Desconocido"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instantánea"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"No hay almacenamiento externo disponible."</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Vista de tira película"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Vista de cuadrícula"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Pantalla completa"</string>
     <string name="trimming" msgid="9122385768369143997">"Recortando"</string>
+    <string name="muting" msgid="5094925919589915324">"Silenciando"</string>
     <string name="please_wait" msgid="7296066089146487366">"Espera."</string>
-    <string name="save_into" msgid="4960537214388766062">"Guardar video recortado en el álbum:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Guardando video en <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"No se puede recortar: el video de destino es demasiado corto."</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Creando panorama..."</string>
     <string name="save" msgid="613976532235060516">"Guardar"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Examinando contenido..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elementos examinados"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d elemento examinado"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elementos examinados"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Ordenando..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"El contenido terminó de examinarse."</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importando..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"No hay contenido disponible para importar a este dispositivo."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"No hay dispositivos MTP conectados."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Error de cámara"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"No se puede establecer conexión con la cámara."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"La cámara ha sido desactivada por políticas de seguridad."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Cámara"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Cámara de video"</string>
+    <string name="wait" msgid="8600187532323801552">"Espera, por favor..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Activa el almacenamiento USB antes de usar la cámara."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Inserta una tarjeta SD antes de usar la cámara."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparando almacenamiento USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparando la tarjeta SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"No se pudo acceder al almacenamiento USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"No se pudo acceder a la tarjeta SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCELAR"</string>
+    <string name="review_ok" msgid="1156261588693116433">"LISTO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Grabación a intervalos de tiempo"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Elegir cámara"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Parte trasera"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Parte delantera"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Almacenar ubicación"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"UBICACIÓN"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Temp. de cuenta regresiva"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 segundo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d segundos"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Pitido en cuenta"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Desactivada"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Activada"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Calidad del video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Baja"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervalo"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Configuración de cámara"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Configuración de videocámara"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Tamaño de imagen"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapíxeles"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 MP"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 MP"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 MP"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 MP"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Modo de enfoque"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automático"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinito"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMÁTICO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODO DE FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automático"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Activado"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Desactivado"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTOMÁTICO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ACTIVADO"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DESACTIVADO"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balance blancos"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANCE DE BLANCOS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automático"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescente"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Luz del día"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescente"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nublado"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMÁTICO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUZ NATURAL"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUBLADO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modo de preselección de escenas"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automático"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acción"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nocturno"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Atardecer"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fiesta"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NINGUNO"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACCIÓN"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOCHE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ATARDECER"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FIESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"TEMPORIZADOR DE CUENTA REGRESIVA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORIZADOR DESACTIVADO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGUNDOS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"No se puede seleccionar en el modo Escena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Valor de exposición"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSICIÓN"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CÁMARA FRONTAL"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CÁMARA POSTERIOR"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Aceptar"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"El almacenamiento USB se está quedando sin espacio. Cambia la configuración de calidad o elimina algunas imágenes u otros archivos."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Tu tarjeta SD se está quedando sin espacio. Cambia la configuración de calidad o elimina algunas imágenes u otros archivos."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Se alcanzó el límite del tamaño."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Muy rápido"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Preparando el panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"No se pudo guardar la im. panorámica."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Capturando el panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Esperando el panorama anterior"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Guardando..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Creando panorama..."</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Toca para enfocar."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efectos"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ninguno"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Comprimir"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Ojos grandes"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Boca grande"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Boca pequeña"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nariz grande"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Ojos pequeños"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"En el espacio"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Atardecer"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Tu video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Desactiva el dispositivo."\n"Deja de usarlo durante unos minutos."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Toca para tomar una foto mientras grabas un video."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Comenzó la grabación de video."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Se detuvo la grabación de video."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Las instantáneas de video se inhabilitan al activar los efectos."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Eliminar efectos"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"CARAS GRACIOSAS"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FONDO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Botón del obturador"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Botón de menú"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto más reciente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Selector de cámara delantera y trasera"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Selector de cámara, video o panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Más controles de configuración"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Cerrar controles de configuración"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Control de zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Disminución de %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Aumentar %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Casilla de verificación %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Cambiar al modo Cámara"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Cambiar al modo Video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Cambiar al modo Panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Cambiar a nuevo panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Cancelar"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Listo"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Revisar nueva toma"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Reproducir video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pausar video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Volver a cargar video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra de tiempo del reproductor de video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ACTIVADO"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DESACTIVADO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Apagado"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 horas"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"segundos"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutos"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"horas"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Listo"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Establecer intervalo de tiempo"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"La función de intervalo de tiempo está desactivada. Actívala para definir el intervalo de tiempo."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"El temporizador de cuenta regresiva está desactivado. Actívalo en la cuenta regresiva antes de tomar una foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Configurar la duración en segundos"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Cuenta regresiva para tomar una foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"¿Recordar ubicaciones de las fotos?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta tus fotos y videos con la ubicación donde fueron tomados."\n\n"Otras aplicaciones pueden acceder a esta información junto con tus imágenes guardadas."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"No, gracias"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Cámara"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Búsqueda"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Álbumes"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MÁS OPCIONES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"CONFIGURACIÓN"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-es/filtershow_strings.xml b/res/values-es/filtershow_strings.xml
index 61d3a3a..6c240dc 100644
--- a/res/values-es/filtershow_strings.xml
+++ b/res/values-es/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Error al cargar la imagen"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Estableciendo fondo de pantalla..."</string>
+    <string name="download_failure" msgid="5923323939788582895">"No se ha podido descargar la foto porque la red no está disponible."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Margen"</string>
-    <string name="done" msgid="3112344807927554662">"Listo"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Deshacer"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Rehacer"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostrar historial"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ocultar historial"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Mostrar estado imagen"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ocultar estado de la imagen"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostrar efectos aplicados"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ocultar efectos aplicados"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Ajustes"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Hay cambios sin guardar en esta imagen."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"¿Quieres guardar antes de salir?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Guardar y salir"</string>
+    <string name="exit" msgid="242642957038770113">"Salir"</string>
     <string name="history" msgid="455767361472692409">"Historial"</string>
     <string name="reset" msgid="9013181350779592937">"Restablecer"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Estado de imagen actual"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efectos aplicados"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Restablecer"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ninguno"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fijo"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Pequeño planeta"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposición"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidez"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Color automático"</string>
     <string name="hue" msgid="6231252147971086030">"Tonalidad"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Lo más destacado"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curvar"</string>
     <string name="vignette" msgid="934721068851885390">"Viñeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Ojos rojos"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Dibujo"</string>
     <string name="straighten" msgid="26025591664983528">"Enderezar"</string>
     <string name="crop" msgid="5781263790107850771">"Recortar"</string>
     <string name="rotate" msgid="2796802553793795371">"Girar"</string>
     <string name="mirror" msgid="5482518108154883096">"Espejo"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativo"</string>
     <string name="none" msgid="6633966646410296520">"Ninguno"</string>
+    <string name="edge" msgid="7036064886242147551">"Bordes"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Reducir calidad"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rojo"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Azul"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estilo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Tamaño"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Color"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Líneas"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Rotulador"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Manchas"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Borrar"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Elegir un color personalizado"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Seleccionar color"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Seleccionar tamaño"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Aceptar"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultado"</string>
 </resources>
diff --git a/res/values-es/photoeditor_strings.xml b/res/values-es/photoeditor_strings.xml
deleted file mode 100644
index 520800b..0000000
--- a/res/values-es/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"No se puede cargar la foto."</string>
-    <string name="saving_failure" msgid="8229491575433743974">"No se ha podido guardar la foto editada."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"La foto editada se ha guardado en <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"¿Descartar los cambios no guardados?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sí"</string>
-    <string name="save" msgid="5516670392524294967">"GUARDAR"</string>
-    <string name="autofix" msgid="1663414996270538748">"Ajuste automático"</string>
-    <string name="crop" msgid="7598378507763334041">"Recortar"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proceso cruzado"</string>
-    <string name="documentary" msgid="50396326708699797">"Documental"</string>
-    <string name="doodle" msgid="1686409894518940990">"Garabatos"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dos tonos"</string>
-    <string name="facelift" msgid="6205748523156172637">"Brillo facial"</string>
-    <string name="facetan" msgid="4412831806626044111">"Bronceado"</string>
-    <string name="filllight" msgid="2644989991700022526">"Luz de relleno"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Ojo de pez"</string>
-    <string name="flip" msgid="2357692401826287480">"Dar la vuelta"</string>
-    <string name="grain" msgid="7487585304579789098">"Grano de película"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Blanco y negro"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Blanco y negro"</string>
-    <string name="highlight" msgid="3902653944386623972">"Reflejos"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomografía"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizar"</string>
-    <string name="redeye" msgid="4958448806369928239">"Ojos rojos"</string>
-    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturación"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Enfocar"</string>
-    <string name="straighten" msgid="5217801513491493491">"Enderezar"</string>
-    <string name="temperature" msgid="1607987938521534517">"Calidez"</string>
-    <string name="tint" msgid="154435943863418434">"Tinte"</string>
-    <string name="vignette" msgid="7648125924662648282">"Viñeta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Arrastra los marcadores para recortar la foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Dibuja en la foto para hacer garabatos"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Arrastra la foto para darle la vuelta"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Elimina los ojos rojos tocando el icono"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Arrastra la foto para girarla"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Arrastra la foto para enderezarla"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efectos de exposición"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efectos de color"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efectos artísticos"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Corregir"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Deshacer"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Rehacer"</string>
-</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 7bec9cd..a737ace 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Girar a la derecha"</string>
     <string name="no_such_item" msgid="5315144556325243400">"No se ha podido encontrar el elemento."</string>
     <string name="edit" msgid="1502273844748580847">"Editar"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edición simple"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Procesando solicitudes de almacenamiento en caché"</string>
     <string name="caching_label" msgid="4521059045896269095">"Almacenando en caché..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
     <string name="trim_action" msgid="703098114452883524">"Recortar"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Silenciar"</string>
     <string name="set_as" msgid="3636764710790507868">"Establecer como"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"No se puede silenciar el vídeo."</string>
     <string name="video_err" msgid="7003051631792271009">"No se puede reproducir el vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Por ubicación"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Por fecha"</string>
@@ -113,7 +116,7 @@
     <string name="no_albums_alert" msgid="4111744447491690896">"No hay álbumes disponibles."</string>
     <string name="empty_album" msgid="4542880442593595494">"No hay imágenes ni vídeos disponibles."</string>
     <string name="picasa_posts" msgid="1497721615718760613">"Publicaciones"</string>
-    <string name="make_available_offline" msgid="5157950985488297112">"Disponible sin conex."</string>
+    <string name="make_available_offline" msgid="5157950985488297112">"Disponible sin conexión"</string>
     <string name="sync_picasa_albums" msgid="8522572542111169872">"Actualizar"</string>
     <string name="done" msgid="217672440064436595">"Listo"</string>
     <string name="sequence_in_set" msgid="7235465319919457488">"Elemento: %1$d de %2$d"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automático"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash activado"</string>
     <string name="flash_off" msgid="1445443413822680010">"Sin flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Desconocido"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instantánea"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"No hay almacenamiento externo disponible."</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Vista de tira de película"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Vista de cuadrícula"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Pantalla completa"</string>
     <string name="trimming" msgid="9122385768369143997">"Recortando..."</string>
+    <string name="muting" msgid="5094925919589915324">"Silenciando..."</string>
     <string name="please_wait" msgid="7296066089146487366">"Espera..."</string>
-    <string name="save_into" msgid="4960537214388766062">"Guardando el vídeo recortado en el álbum:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Guardando vídeo en <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"No se puede recortar: el vídeo de destino es demasiado corto."</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Creando panorámica..."</string>
     <string name="save" msgid="613976532235060516">"Guardar"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Analizando contenido..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elementos analizados"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d elemento analizado"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elementos analizados"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Ordenando..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Análisis completo"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importando..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"No hay contenido disponible para importar en este dispositivo."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"No hay dispositivos MTP conectados"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Error de cámara"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"No se puede acceder a la cámara."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Se ha inhabilitado la cámara debido a las políticas de seguridad."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Cámara"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videocámara"</string>
+    <string name="wait" msgid="8600187532323801552">"Por favor, espera..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Para poder usar la cámara, activa el almacenamiento USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Para poder usar la cámara, inserta una tarjeta SD."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparando almacenamiento USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparando tarjeta SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"No se ha podido acceder al almacenamiento USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"No se ha podido acceder a la tarjeta SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCELAR"</string>
+    <string name="review_ok" msgid="1156261588693116433">"LISTO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Grabación a intervalos de tiempo"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Seleccionar cámara"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Trasera"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Delantera"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Añadir ubicación"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"UBICACIÓN"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Temporizador de cuenta atrás"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 segundo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d segundos"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Utilizar pitido"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Desactivado"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Activado"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Calidad de vídeo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Baja"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervalo de tiempo"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Ajustes de la cámara"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Configuración de videocámara"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Tamaño imagen"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 MP"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 MP"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 MP"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 MP (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 MP"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 MP"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Modo de enfoque"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinito"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMÁTICO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODO FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automático"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Activado"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Desactivado"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTOMÁTICO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ACTIVADO"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DESACTIVADO"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balance de blancos"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANCE DE BLANCOS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automático"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescente"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Luz natural"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescente"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nublado"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMÁTICO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUZ NATURAL"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUBLADO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modo de escena"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automático"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acción"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nocturno"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Atardecer"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fiesta"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NINGUNO"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACCIÓN"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOCHE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ATARDECER"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FIESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"TEMPORIZADOR DE CUENTA ATRÁS"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORIZADOR DESACTIVADO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGUNDOS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"No se puede seleccionar en el modo de escena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposición"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSICIÓN"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"ARD"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CÁMARA FRONTAL"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CÁMARA TRASERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Aceptar"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"No queda espacio en el almacenamiento USB. Cambia la configuración de calidad o elimina algunas imágenes u otros archivos."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"No queda espacio en la tarjeta SD. Cambia la configuración de calidad o elimina algunas imágenes u otros archivos."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Se ha alcanzado el límite de tamaño."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Muy rápido"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Preparando modo panorámico"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Error al guardar imagen panorámica"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorámico"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Capturando panorámica"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Esperando foto panorámica anterior..."</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Guardando..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Creando panorámica..."</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Toca para enfocar"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efectos"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ninguno"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Comprimir"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Ojos grandes"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Boca grande"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Boca pequeña"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nariz grande"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Ojos pequeños"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"En el espacio"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Atardecer"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Tu vídeo"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Desactiva el dispositivo."\n"Deja de usarlo durante unos minutos."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Toca para hacer una foto mientras grabas un vídeo."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Se ha iniciado la grabación de vídeo."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"La grabación de vídeo se ha detenido."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"La instantánea de vídeo se inhabilita al activar efectos especiales."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Borrar efectos"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"CARAS GRACIOSAS"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FONDO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Botón del obturador"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Botón de menú"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto más reciente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Opción de cámara trasera y delantera"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Cámara, vídeo o modo panorámico"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Más controles de configuración"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Cerrar controles de configuración"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Control de zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Reducir %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Aumentar %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Casilla de verificación %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Cambiar a la cámara"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Cambiar a vídeo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Cambiar a modo panorámico"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Cambiar a nueva panorámica"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Cancelar"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Listo"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Revisar repetición"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Ver un vídeo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pausar vídeo"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Volver a cargar vídeo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra de tiempo del reproductor de vídeo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"SÍ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"NO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Desactivado"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 horas"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"segundos"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutos"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"horas"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Listo"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Establecer intervalo de tiempo"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"El intervalo de tiempo está desactivado. Activa esta función para establecer un intervalo."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"El temporizador de cuenta atrás está desactivado. Activa esta opción para ver la cuenta atrás antes de hacer una foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Definir duración en segundos"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Cuenta atrás para hacer una foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"¿Recordar ubicaciones de las fotos?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Etiqueta tus fotos y vídeos con las ubicaciones donde se han realizado."\n\n"Otras aplicaciones pueden acceder a esta información, así como a las imágenes guardadas."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"No, gracias"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sí"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Cámara"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Buscar"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Álbumes"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MÁS OPCIONES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"AJUSTES"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-et/filtershow_strings.xml b/res/values-et/filtershow_strings.xml
index 2f585f1..185608b 100644
--- a/res/values-et/filtershow_strings.xml
+++ b/res/values-et/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Pilti ei saa laadida!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Taustapildi määramine"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotot ei õnnestunud alla laadida, kuna võrk ei ole saadaval."</string>
     <string name="original" msgid="3524493791230430897">"Originaal"</string>
     <string name="borders" msgid="2067345080568684614">"Äärised"</string>
-    <string name="done" msgid="3112344807927554662">"Valmis"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Võta tagasi"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Tee uuesti"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Kuva ajalugu"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Peida ajalugu"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Kuva pildi olek"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Peida pildi olek"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Kuva rakendatud efektid"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Peida rakendatud efektid"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Seaded"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Kujutisel on salvestamata muudatusi."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Kas soovite enne väljumist salvestada?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Salvesta ja välju"</string>
+    <string name="exit" msgid="242642957038770113">"Välju"</string>
     <string name="history" msgid="455767361472692409">"Ajalugu"</string>
     <string name="reset" msgid="9013181350779592937">"Lähtesta"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Pildi praegune olek"</string>
+    <string name="imageState" msgid="8632586742752891968">"Rakendatud efektid"</string>
     <string name="compare_original" msgid="8140838959007796977">"Võrdle"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Rakenda"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Lähtesta"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Mitte ühtegi"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fikseeritud"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Pisike planeet"</string>
     <string name="exposure" msgid="6526397045949374905">"Säriaeg"</string>
     <string name="sharpness" msgid="6463103068318055412">"Teravus"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. värvid"</string>
     <string name="hue" msgid="6231252147971086030">"Värvitoon"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Varjud"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Esiletõstmine"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kõverad"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjett"</string>
     <string name="redeye" msgid="4508883127049472069">"Punasilmsus"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Joonistus"</string>
     <string name="straighten" msgid="26025591664983528">"Rihi otseks"</string>
     <string name="crop" msgid="5781263790107850771">"Kärpimine"</string>
     <string name="rotate" msgid="2796802553793795371">"Pööra"</string>
     <string name="mirror" msgid="5482518108154883096">"Peegelpilt"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatiiv"</string>
     <string name="none" msgid="6633966646410296520">"Mitte ühtegi"</string>
+    <string name="edge" msgid="7036064886242147551">"Servad"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Vähend."</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Punane"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Roheline"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Sinine"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stiil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Suurus"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Värv"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Jooned"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Pritsi"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Tühjenda"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Valige kohandatud värv"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Värvi valimine"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Suuruse valimine"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Algne"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Tulemus"</string>
 </resources>
diff --git a/res/values-et/photoeditor_strings.xml b/res/values-et/photoeditor_strings.xml
deleted file mode 100644
index a8ca04d..0000000
--- a/res/values-et/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotostuudio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotot ei õnnestunud laadida"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Muudetud fotot ei õnnestunud salvestada"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Muudetud foto salvestati kausta <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kas loobute salvestamata muudatustest?"</string>
-    <string name="yes" msgid="5402582493291792293">"Jah"</string>
-    <string name="save" msgid="5516670392524294967">"SALVESTA"</string>
-    <string name="autofix" msgid="1663414996270538748">"Automaatparandus"</string>
-    <string name="crop" msgid="7598378507763334041">"Kärpimine"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Risttöötlus"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentaalfilm"</string>
-    <string name="doodle" msgid="1686409894518940990">"Kritseldamine"</string>
-    <string name="duotone" msgid="8145893940788467106">"Kahetooniline"</string>
-    <string name="facelift" msgid="6205748523156172637">"Näonaha ühtlustamine"</string>
-    <string name="facetan" msgid="4412831806626044111">"Näojume"</string>
-    <string name="filllight" msgid="2644989991700022526">"Valguse täitmine"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Kalasilm"</string>
-    <string name="flip" msgid="2357692401826287480">"Ümberpööramine"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmi teralisus"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Mustvalge"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Mustvalge"</string>
-    <string name="highlight" msgid="3902653944386623972">"Esiletõstud"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatiivne"</string>
-    <string name="posterize" msgid="4139212359561383385">"Plakatiks"</string>
-    <string name="redeye" msgid="4958448806369928239">"Punasilmsus"</string>
-    <string name="rotate" msgid="6607597269792373083">"Pööramine"</string>
-    <string name="saturation" msgid="8621322012271169931">"Küllastus"</string>
-    <string name="sepia" msgid="7978093531824705601">"Seepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Varjud"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Teravustamine"</string>
-    <string name="straighten" msgid="5217801513491493491">"Rihtimine"</string>
-    <string name="temperature" msgid="1607987938521534517">"Soojus"</string>
-    <string name="tint" msgid="154435943863418434">"Heledusvarjund"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjett"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Foto kärpimiseks lohistage markereid"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Kritseldamiseks joonistage fotol"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Ümberpööramiseks lohistage fotot"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Punasilmsuse eemaldam. puudutage silma"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Pööramiseks lohistage fotot"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Rihtimiseks lohistage fotot"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Säritusefektid"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Värviefektid"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Kunstilised efektid"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Paranda"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Võta tagasi"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Tee uuesti"</string>
-</resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 04cce4a..5405607 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Pööra paremale"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Üksust ei leitud."</string>
     <string name="edit" msgid="1502273844748580847">"Muuda"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Lihtne muutmine"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Vahemällu lisamise taotluste töötlemine"</string>
     <string name="caching_label" msgid="4521059045896269095">"Vahemällu ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Kärbi"</string>
     <string name="trim_action" msgid="703098114452883524">"Kärbi"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Vaigista"</string>
     <string name="set_as" msgid="3636764710790507868">"Seadista kui"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Video vaigistamine ei õnnestu."</string>
     <string name="video_err" msgid="7003051631792271009">"Videot ei saa esitada."</string>
     <string name="group_by_location" msgid="316641628989023253">"Asukoha järgi"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Aja järgi"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Autom."</string>
     <string name="flash_on" msgid="7891556231891837284">"Välk sees"</string>
     <string name="flash_off" msgid="1445443413822680010">"Välguta"</string>
+    <string name="unknown" msgid="3506693015896912952">"Tundmatu"</string>
     <string name="ffx_original" msgid="372686331501281474">"Originaal"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Ükski välismäluseade ei ole saadaval"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmiribakuva"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Ruudustiku kuva"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Täisekraani vaade"</string>
     <string name="trimming" msgid="9122385768369143997">"Kärpimine"</string>
+    <string name="muting" msgid="5094925919589915324">"Vaigistamine"</string>
     <string name="please_wait" msgid="7296066089146487366">"Palun oodake"</string>
-    <string name="save_into" msgid="4960537214388766062">"Kärbitud video salvestamine albumisse:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Video salvestamine albumisse <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Ei saa kärpida: lõppvideo on liiga lühike"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panoraami renderdamine"</string>
     <string name="save" msgid="613976532235060516">"Salvesta"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Sisu skannimine ..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d üksust on skannitud"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d üksus on skannitud"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d üksust on skannitud"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sortimine ..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skannimine on lõppenud"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importimine ..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Seadmes pole importimiseks saadaval mingit sisu."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Ühtegi MTP-seadet pole ühendatud"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kaamera viga"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Ei saa kaameraga ühendada."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kaamera on keelatud turvaeeskirjade tõttu."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kaamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokaamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Oodake ..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Paigaldage USB-mäluseade enne kaamera kasutamist."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Enne kaamera kasutamist sisestage SD-kaart."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB-seadme ettevalmistamine…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD-kaardi ettevalmistamine ..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Juurdepääs USB-mäluseadmele ebaõnnestus."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Juurdepääs SD-kaardile ebaõnnestus."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"TÜHISTA"</string>
+    <string name="review_ok" msgid="1156261588693116433">"VALMIS"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Aeglase filmimise salvestamine"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Vali kaamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Tagasi"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Eestvaade"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Talletuse asukoht"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"ASUKOHT"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Iseavaja taimer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekund"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sekundit"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Iseavaja heli"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Väljas"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Sees"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Video kvaliteet"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Kõrge"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Madal"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Aeglustus"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kaamera seaded"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokaamera seaded"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Pildi suurus"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapikslit"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapikslit"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M pikslit"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapikslit"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M pikslit"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M pikslit"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Mpiks. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3M pikslit"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M pikslit"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Teravustamisrežiim"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automaatne"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Lõpmatus"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMAATNE"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"LÕPMATUS"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Välgurežiim"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"VÄLGUREŽIIM"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automaatne"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Sees"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Väljas"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMAATVÄLK"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"VÄLK ON SEES"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"VÄLK ON VÄLJAS"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Valge tasakaal"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"VALGE TASAKAAL"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automaatne"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Hõõglamp"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Päevavalgus"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Päevavalguslamp"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Pilves"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMAATNE"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"HÕÕGLAMP"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"PÄEVAVALGUS"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"PÄEVAVALGUSLAMP"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"PILVINE"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Stseenirežiim"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automaatne"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Toiming"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Öö"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Päikeseloojang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Pidu"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"PUUDUB"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"TOIMING"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ÖÖ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"PÄIKESELOOJANG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PIDU"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"PÖÖRDLOENDUSTAIMER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TAIMER VÄLJA"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUND"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDIT"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDIT"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDIT"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Seda ei saa stseenirežiimis valida."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Säriaeg"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"SÄRIAEG"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ESIKAAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"TAGAKAAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Teie USB-mäluseadme ruum on otsa saamas. Muutke kvaliteediseadeid või kustutage kujutisi või teisi faile."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Teie SD-kaardi ruum on otsa saamas. Muutke kvaliteedi seadeid või kustutage kujutisi või teisi faile."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Suuruspiirang on saavutatud."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Liiga kiire"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panoraami ettevalmistus"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panoraami ei saanud salvestada."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoraam"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panoraami jäädvustamine"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Eelmise panoraami ootel"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Salvestus ..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panoraami renderdamine"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Puudutage fokuseerimiseks."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efektid"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Mitte ühtegi"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Pitsita"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Suured silmad"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Suur suu"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Väike suu"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Suur nina"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Väiksed silmad"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Kosmoses"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Päikeseloojang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Teie video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Pange seade käest ära."\n"Astuge korraks vaateväljast eemale."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Puudutage salvestamise ajal foto jäädvustamiseks."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video salvestamine algas."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video salvestamine lõppes."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Kui eriefektid on sisse lülitatud, on video hetktõmmis keelatud."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Nulli efektid"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"NALJAKAD NÄOD"</string>
+    <string name="effect_background" msgid="6579360207378171022">"TAUST"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Päästiku nupp"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menüü nupp"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Viimane foto"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Eesmise ja tagumise kaamera lüliti"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kaamera, video või panoraami valija"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Rohkem seadete juhtnuppe"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Sule seadete juhtnupud"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Suumi juhtimine"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Vähenda %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Suurenda %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s märkeruut"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Aktiveeri fotorežiim"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Aktiveeri videorežiim"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Aktiveeri panoraamrežiim"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Lülitu uuele panoraamile"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Arvustuse tühistamine"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Arvustus valmis"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Uue võtte ülevaade"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Esita videot"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Peata video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Laadi video uuesti"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Videomängija ajariba"</string>
+    <string name="capital_on" msgid="5491353494964003567">"SEES"</string>
+    <string name="capital_off" msgid="7231052688467970897">"VÄLJAS"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Väljas"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekundit"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 tund"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 tundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 tundi"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekundit"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutit"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"tundi"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Valmis"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Ajavahemiku määramine"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Aeglustamisfunktsioon on välja lülitatud. Ajavahemiku määramiseks lülitage funktsioon sisse."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Iseavaja taimer on väljas. Lülitage see sisse, et loendada pildi tegemiseni jäänud aega."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Määrake kestus sekundites"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Aega foto tegemiseni"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Kas jätta meelde fotode jäädvustamise asukohad?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Märkige oma fotodele ja videotele jäädvustamise asukoht."\n\n"Muud rakendused pääsevad lisaks salvestatud piltidele juurde ka sellele teabele."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ei, tänan"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Jah"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kaamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Otsing"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotod"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumid"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ROHKEM VALIKUID"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"SEADED"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotot"</item>
+  </plurals>
 </resources>
diff --git a/res/values-fa/filtershow_strings.xml b/res/values-fa/filtershow_strings.xml
index e34823b..4ac82a6 100644
--- a/res/values-fa/filtershow_strings.xml
+++ b/res/values-fa/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"تصویر بارگیری نمی‌شود!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"تنظیم تصویر زمینه"</string>
+    <string name="download_failure" msgid="5923323939788582895">"دانلود عکس انجام نشد. شبکه در دسترس نیست."</string>
     <string name="original" msgid="3524493791230430897">"اصلی"</string>
     <string name="borders" msgid="2067345080568684614">"حاشیه‌ها"</string>
-    <string name="done" msgid="3112344807927554662">"انجام شد"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"لغو عمل"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"انجام مجدد"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"نمایش سابقه"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"پنهان کردن سابقه"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"نمایش وضعیت تصویر"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"پنهان کردن وضعیت تصویر"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"نمایش جلوه‌های اعمال شده"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"پنهان کردن جلوه‌های اعمال شده"</string>
     <string name="menu_settings" msgid="6428291655769260831">"تنظیمات"</string>
+    <string name="unsaved" msgid="8704442449002374375">"تغییرات ذخیره نشده‌ای در این تصویر وجود دارد."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"آیا می‌خواهید پیش از خروج تغییرات ذخیره شود؟"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"ذخیره و خروج"</string>
+    <string name="exit" msgid="242642957038770113">"خروج"</string>
     <string name="history" msgid="455767361472692409">"سابقه"</string>
     <string name="reset" msgid="9013181350779592937">"بازنشانی"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"وضعیت کنونی تصویر"</string>
+    <string name="imageState" msgid="8632586742752891968">"جلوه‌های اعمال شده"</string>
     <string name="compare_original" msgid="8140838959007796977">"مقایسه"</string>
     <string name="apply_effect" msgid="1218288221200568947">"اعمال‌ کردن"</string>
     <string name="reset_effect" msgid="7712605581024929564">"بازنشانی"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"هیچکدام"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"ثابت"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"سیاره کوچک"</string>
     <string name="exposure" msgid="6526397045949374905">"نوردهی"</string>
     <string name="sharpness" msgid="6463103068318055412">"وضوح"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"رنگ خودکار"</string>
     <string name="hue" msgid="6231252147971086030">"رنگ‌مایه"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"سایه‌ها"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"هایلایت"</string>
     <string name="curvesRGB" msgid="915010781090477550">"نمودارها"</string>
     <string name="vignette" msgid="934721068851885390">"محو لبه‌ها"</string>
     <string name="redeye" msgid="4508883127049472069">"قرمزی چشم"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"طراحی"</string>
     <string name="straighten" msgid="26025591664983528">"صاف‌ کردن"</string>
     <string name="crop" msgid="5781263790107850771">"برش"</string>
     <string name="rotate" msgid="2796802553793795371">"چرخش"</string>
     <string name="mirror" msgid="5482518108154883096">"معکوس کردن"</string>
+    <string name="negative" msgid="6998313764388022201">"نگاتیو"</string>
     <string name="none" msgid="6633966646410296520">"هیچکدام"</string>
+    <string name="edge" msgid="7036064886242147551">"لبه‌ها"</string>
+    <string name="kmeans" msgid="1630263230946107457">"وارهول"</string>
+    <string name="downsample" msgid="3552938534146980104">"کاهش وضوح"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"قرمز"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"سبز"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"آبی"</string>
+    <string name="draw_style" msgid="2036125061987325389">"سبک"</string>
+    <string name="draw_size" msgid="4360005386104151209">"اندازه"</string>
+    <string name="draw_color" msgid="2119030386987211193">"رنگ"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"خطوط"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"نشانگر"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"سبک پاشش"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"پاک کردن"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"رنگ سفارشی را انتخاب کنید"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"رنگ را انتخاب کنید"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"انتخاب اندازه"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"تأیید"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"اصلی"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"نتیجه"</string>
 </resources>
diff --git a/res/values-fa/photoeditor_strings.xml b/res/values-fa/photoeditor_strings.xml
deleted file mode 100644
index b9d1c96..0000000
--- a/res/values-fa/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"بارگیری عکس انجام نشد"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"عکس ویرایش شده ذخیره نشد"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"عکس ویرایش شده در <xliff:g id="FOLDER_NAME">%s</xliff:g> ذخیره شد"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"از تغییرات ذخیره نشده صرفنظر شود؟"</string>
-    <string name="yes" msgid="5402582493291792293">"بله"</string>
-    <string name="save" msgid="5516670392524294967">"ذخیره"</string>
-    <string name="autofix" msgid="1663414996270538748">"تصحیح خودکار"</string>
-    <string name="crop" msgid="7598378507763334041">"برش"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"فرآیند همگذاری"</string>
-    <string name="documentary" msgid="50396326708699797">"مستند"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"دو تنالیته رنگ"</string>
-    <string name="facelift" msgid="6205748523156172637">"صورت براق"</string>
-    <string name="facetan" msgid="4412831806626044111">"صورت برنزه"</string>
-    <string name="filllight" msgid="2644989991700022526">"پر کردن نور"</string>
-    <string name="fisheye" msgid="6037488646928998921">"فیش آی"</string>
-    <string name="flip" msgid="2357692401826287480">"وارونه کردن"</string>
-    <string name="grain" msgid="7487585304579789098">"بافت فیلم"</string>
-    <string name="grayscale" msgid="7641563843060945228">"سیاه‌ و سفید"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"سیاه و سفید"</string>
-    <string name="highlight" msgid="3902653944386623972">"قسمت‌های برجسته"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"نگاتیو"</string>
-    <string name="posterize" msgid="4139212359561383385">"پوستری کردن"</string>
-    <string name="redeye" msgid="4958448806369928239">"قرمزی چشم"</string>
-    <string name="rotate" msgid="6607597269792373083">"چرخش"</string>
-    <string name="saturation" msgid="8621322012271169931">"اشباع"</string>
-    <string name="sepia" msgid="7978093531824705601">"سپیا"</string>
-    <string name="shadow" msgid="8235188588101973090">"سایه ها"</string>
-    <string name="sharpen" msgid="8449662378104403230">"وضوح"</string>
-    <string name="straighten" msgid="5217801513491493491">"صاف کردن"</string>
-    <string name="temperature" msgid="1607987938521534517">"گرما"</string>
-    <string name="tint" msgid="154435943863418434">"ته رنگ"</string>
-    <string name="vignette" msgid="7648125924662648282">"وینت"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"برای بریدن عکس نشانگرها را بکشید"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"برای doodle کردن روی عکس رسم کنید"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"برای وارونه کردن عکس آن را بکشید"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"روی قرمزی چشم برای رفع آن ضربه بزنید"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"برای چرخاندن عکس آن را بکشید"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"برای صاف کردن عکس آن را بکشید"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"جلوه‌های نوردهی"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"جلوه‌های رنگی"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"جلوه‌های هنری"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"تصحیح"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"لغو"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"انجام مجدد"</string>
-</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index fab9176..d586aaf 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"چرخش به راست"</string>
     <string name="no_such_item" msgid="5315144556325243400">"مورد یافت نشد."</string>
     <string name="edit" msgid="1502273844748580847">"ویرایش"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"ویرایش ساده"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"پردازش درخواست‌های ذخیره در حافظهٔ پنهان"</string>
     <string name="caching_label" msgid="4521059045896269095">"در حال ذخیره در حافظهٔ پنهان..."</string>
     <string name="crop_action" msgid="3427470284074377001">"برش"</string>
     <string name="trim_action" msgid="703098114452883524">"برش"</string>
+    <string name="mute_action" msgid="5296241754753306251">"بی‌صدا"</string>
     <string name="set_as" msgid="3636764710790507868">"تنظیم به‌عنوان"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"بیصدا کردن ویدیو انجام نشد."</string>
     <string name="video_err" msgid="7003051631792271009">"ویدئو پخش نمی‌شود."</string>
     <string name="group_by_location" msgid="316641628989023253">"بر اساس محل"</string>
     <string name="group_by_time" msgid="9046168567717963573">"بر اساس زمان"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"خودکار"</string>
     <string name="flash_on" msgid="7891556231891837284">"فلاش زده شد"</string>
     <string name="flash_off" msgid="1445443413822680010">"بدون فلاش"</string>
+    <string name="unknown" msgid="3506693015896912952">"ناشناس"</string>
     <string name="ffx_original" msgid="372686331501281474">"اصلی"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"آنتیک"</string>
     <string name="ffx_instant" msgid="726968618715691987">"فوری"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"هیچ دستگاه ذخیره خارجی دردسترس نیست"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"نمایش نوار فیلم"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"نمای شبکه‌ای"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"نمایش تمام صفحه"</string>
     <string name="trimming" msgid="9122385768369143997">"کوتاه کردن"</string>
+    <string name="muting" msgid="5094925919589915324">"بیصدا کردن"</string>
     <string name="please_wait" msgid="7296066089146487366">"لطفاً منتظر بمانید"</string>
-    <string name="save_into" msgid="4960537214388766062">"ذخیره ویدیوی کوتاه شده در آلبوم:"</string>
+    <string name="save_into" msgid="9155488424829609229">"درحال ذخیره ویدیو در <xliff:g id="ALBUM_NAME">%1$s</xliff:g> ..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"نمی‌تواند کوتاه شود: ویدیوی موردنظر بسیار کوتاه است"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"در حال تولید تصویر پانوراما"</string>
     <string name="save" msgid="613976532235060516">"ذخیره"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"در حال اسکن کردن محتوا..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d مورد اسکن شد"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d مورد اسکن شد"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d مورد اسکن شد"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"در حال مرتب‌سازی..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"اسکن انجام شد"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"درحال وارد کردن..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"هیچ محتوایی برای وارد کردن در این دستگاه، موجود نیست."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"هیچ دستگاه MTP متصل نیست"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"خطای دوربین"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"اتصال به دوربین امکان‌پذیر نیست."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"به دلیل خط مشی‌های امنیتی، دوربین غیرفعال شده است."</string>
+    <string name="camera_label" msgid="6346560772074764302">"دوربین"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"دوربین فیلمبرداری"</string>
+    <string name="wait" msgid="8600187532323801552">"لطفاً منتظر بمانید…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"قبل از استفاده از دوربین حافظهٔ USB را وصل کنید."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"قبل از استفاده از دوربین یک کارت SD وارد کنید."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"آماده سازی حافظهٔ USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"در حال آماده سازی کارت SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"دسترسی به حافظهٔ USB ممکن نیست."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"دسترسی به کارت SD ممکن نیست."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"لغو"</string>
+    <string name="review_ok" msgid="1156261588693116433">"انجام شد"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"زمان سپری شده ضبط"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"انتخاب دوربین"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"برگشت"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"جلو"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"ذخیره موقعیت مکانی"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"موقعیت مکانی"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"تایمر شمارش معکوس"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"۱ ثانیه"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d ثانیه"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"بیپ هنگام شمارش معکوس"</string>
+    <string name="setting_off" msgid="4480039384202951946">"غیرفعال"</string>
+    <string name="setting_on" msgid="8602246224465348901">"فعال"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"کیفیت ویدئو"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"بالا"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"کم"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"زمان سپری شده"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"تنظیمات دوربین"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"تنظیمات دوربین فیلمبرداری"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"اندازه تصویر"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"۱۳ مگا پیکسل"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"۸ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"۵ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"۴ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"۳ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"۲ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"۲ مگاپیکسل (۱۶:۹)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"۱.۳ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"۱ مگاپیکسل"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"حالت فوکوس"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"خودکار"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"بی نهایت"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"ماکرو"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"خودکار"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"بی‌نهایت"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"ماکرو"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"حالت فلاش"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"حالت فلاش"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"خودکار"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"روشن"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"خاموش"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"فلاش خودکار"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"فلاش روشن"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"فلاش خاموش"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"توازن سفیدی"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"توازن سفیدی"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"خودکار"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"تابان"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"روشنایی روز"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"فلورسنت"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"ابری"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"خودکار"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"تابان"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"روشنایی روز"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"فلورسنت"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ابری"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"حالت منظره"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"خودکار"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"عملکرد"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"شب"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"غروب آفتاب"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"طرف مقابل"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"هیچکدام"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"عملکرد"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"شب"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"غروب"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"مهمانی"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"تایمر شمارش معکوس"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"تایمر خاموش است"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"۱ ثانیه"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"۳ ثانیه"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"۱۰ ثانیه"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"۱۵ ثانیه"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"در حالت صحنه قابل انتخاب نیست."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"نوردهی"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"نوردهی"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"دوربین جلو"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"دوربین پشت"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"تأیید"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"حافظهٔ USB پر است. تنظیمات کیفیت را تغییر دهید یا برخی تصاویر یا سایر فایل‌ها را حذف کنید."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"کارت SD شما پر شده است. تنظیمات کیفیت را تغییر دهید یا برخی تصاویر یا فایل‌های دیگر را حذف کنید."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"بیش از حداکثر مجاز."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"بسیار سریع"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"تهیه پانوراما"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"ذخیره پانوراما امکان‌پذیر نیست."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"پانوراما"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"گرفتن عکس پانوراما"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"در حال انتظار برای پانورامای قبلی"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"در حال ذخیره..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"در حال تولید تصویر پانوراما"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"برای فوکوس لمس کنید."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"جلوه‌ها"</string>
+    <string name="effect_none" msgid="3601545724573307541">"هیچکدام"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"کوچک کردن"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"چشمان بزرگ"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"دهان بزرگ"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"دهان کوچک"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"بینی بزرگ"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"چشمان کوچک"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"در فضا"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"غروب آفتاب"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"ویدئوی شما"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"دستگاه خود را کنار بگذارید."\n"برای یک لحظه از دید خارج شوید."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"برای گرفتن عکس در هنگام ضبط لمس کنید."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"ضبط ویدیو شروع شد."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"ضبط ویدیو متوقف شد."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"هنگام فعال بودن جلوه‌های ویژه، عکس فوری از ویدئو غیرفعال است."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"پاک کردن جلوه‌ها"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"چهره‌های احمقانه"</string>
+    <string name="effect_background" msgid="6579360207378171022">"پس‌زمینه"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"دکمه شاتر"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"دکمه منو"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"جدیدترین عکس"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"کلید دوربین جلو و عقب"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"انتخابگر دوربین، ویدئو یا پانوراما"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"کنترل‌های تنظیم بیشتر"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"بستن کنترل‌های تنظیم"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"کنترل بزرگنمایی"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"کاهش %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"افزایش %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"کادر انتخاب %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"رفتن به عکس"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"رفتن به ویدئو"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"رفتن به پانوراما"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"تغییر به پانورامای جدید"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"لغو بازبینی"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"بازبینی انجام شد"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"بازبینی عکس مجدد"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"پخش ویدیو"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"توقف موقت ویدیو"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"تازه‌سازی ویدیو"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"نوار زمان پخش‌کننده ویدیو"</string>
+    <string name="capital_on" msgid="5491353494964003567">"روشن"</string>
+    <string name="capital_off" msgid="7231052688467970897">"خاموش"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"خاموش"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"۰.۵ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"۱ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"۱.۵ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"۲ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"۲.۵ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"۳ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"۴ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"۵ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"۶ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"۱۰ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"۱۲ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"۱۵ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"۲۴ ثانیه"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"۰.۵ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"۱ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"۱.۵ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"۲ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"۲.۵ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"۳ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"۴ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"۵ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"۶ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"۱۰ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"۱۲ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"۱۵ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"۲۴ دقیقه"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"۰.۵ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"۱ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"۱.۵ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"۲ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"۲.۵ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"۳ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"۴ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"۵ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"۶ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"۱۰ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"۱۲ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"۱۵ ساعت"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"۲۴ ساعت"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"ثانیه"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"دقیقه"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ساعت"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"انجام شد"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"تنظیم فاصله زمانی"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"ویژگی زمان سپری شده خاموش است. برای تنظیم فاصله زمانی آن را روشن کنید."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"تایمر شمارش معکوس خاموش است. قبل از گرفتن تصویر آن را برای شمارش معکوس روشن کنید."</string>
+    <string name="set_duration" msgid="5578035312407161304">"تنظیم مدت زمان به ثانیه"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"شمارش معکوس برای گرفتن یک عکس"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"موقعیت مکانی عکس به‌خاطر سپرده شود؟"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"محل گرفتن عکس‌ها و ویدیوها را به آنها برچسب کنید."\n\n" سایر برنامه‌ها می‌توانند به این اطلاعات در کنار تصاویر ذخیره شده شما دسترسی پیدا کنند."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"نه متشکرم"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"بله"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"دوربین"</string>
+    <string name="menu_search" msgid="7580008232297437190">"جستجو"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"عکس‌ها"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"آلبوم‌ها"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"گزینه‌های بیشتر"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"تنظیمات"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d عکس"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d عکس "</item>
+  </plurals>
 </resources>
diff --git a/res/values-fi/filtershow_strings.xml b/res/values-fi/filtershow_strings.xml
index 38be733..1a9c569 100644
--- a/res/values-fi/filtershow_strings.xml
+++ b/res/values-fi/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Kuvaa ei voi ladata."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Asetetaan taustakuvaa"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Kuvan lataaminen epäonnistui. Verkkoon ei saada yhteyttä."</string>
     <string name="original" msgid="3524493791230430897">"Alkuperäinen"</string>
     <string name="borders" msgid="2067345080568684614">"Reunukset"</string>
-    <string name="done" msgid="3112344807927554662">"Valmis"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Kumoa"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Toista"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Näytä historia"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Piilota historia"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Näytä kuvan tila"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Piilota kuvan tila"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Näytä käytetyt tehosteet"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Piilota käytetyt tehosteet"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Asetukset"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Tähän kuvaan on tehty muutoksia, joita ei ole tallennettu."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Haluatko tallentaa ennen sulkemista?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Tallenna ja sulje"</string>
+    <string name="exit" msgid="242642957038770113">"Sulje"</string>
     <string name="history" msgid="455767361472692409">"Historia"</string>
     <string name="reset" msgid="9013181350779592937">"Palauta"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Kuvan nykyinen tila"</string>
+    <string name="imageState" msgid="8632586742752891968">"Käytetyt tehosteet"</string>
     <string name="compare_original" msgid="8140838959007796977">"Vertaa"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Käytä"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Palauta"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ei mitään"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Kiinteä"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Pikkuplaneetta"</string>
     <string name="exposure" msgid="6526397045949374905">"Valotus"</string>
     <string name="sharpness" msgid="6463103068318055412">"Terävyys"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. värit"</string>
     <string name="hue" msgid="6231252147971086030">"Sävy"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Tummat alueet"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Valokohdat"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Valotuskäyrät"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjetti"</string>
     <string name="redeye" msgid="4508883127049472069">"Punasilmäisyys"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Piirrä"</string>
     <string name="straighten" msgid="26025591664983528">"Suorista"</string>
     <string name="crop" msgid="5781263790107850771">"Rajaa"</string>
     <string name="rotate" msgid="2796802553793795371">"Kierrä"</string>
     <string name="mirror" msgid="5482518108154883096">"Peilikuva"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatiivi"</string>
     <string name="none" msgid="6633966646410296520">"Ei mitään"</string>
+    <string name="edge" msgid="7036064886242147551">"Reunat"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Pienennys"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Punainen"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Vihreä"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Sininen"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Tyyli"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Koko"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Väri"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Viivat"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Tussi"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Roiskeet"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Tyhjennä"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Valitse oma väri"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Valitse väri"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Valitse koko"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Alkuperäinen"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Tulos"</string>
 </resources>
diff --git a/res/values-fi/photoeditor_strings.xml b/res/values-fi/photoeditor_strings.xml
deleted file mode 100644
index 6cb7170..0000000
--- a/res/values-fi/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Valokuvastudio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Kuvaa ei voi ladata"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Muokatun kuvan tallentaminen epäonnistui"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Muokattu kuva tallennettu kansioon <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Hylätäänkö tallentamattomat muutokset?"</string>
-    <string name="yes" msgid="5402582493291792293">"Kyllä"</string>
-    <string name="save" msgid="5516670392524294967">"Tallenna"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autom. korjaus"</string>
-    <string name="crop" msgid="7598378507763334041">"Rajaa"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Ristiinkäsittely"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentti"</string>
-    <string name="doodle" msgid="1686409894518940990">"Piirtely"</string>
-    <string name="duotone" msgid="8145893940788467106">"Kaksoissävy"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Täytä vaaleat"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Kalansilmä"</string>
-    <string name="flip" msgid="2357692401826287480">"Käännä"</string>
-    <string name="grain" msgid="7487585304579789098">"Rakeisuus"</string>
-    <string name="grayscale" msgid="7641563843060945228">"MV"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Mustavalkoinen"</string>
-    <string name="highlight" msgid="3902653944386623972">"Korostus"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatiivinen"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterisaatio"</string>
-    <string name="redeye" msgid="4958448806369928239">"Punasilmäisyys"</string>
-    <string name="rotate" msgid="6607597269792373083">"Kierrä"</string>
-    <string name="saturation" msgid="8621322012271169931">"Värikylläisyys"</string>
-    <string name="sepia" msgid="7978093531824705601">"Seepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Varjostus"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Tarkennus"</string>
-    <string name="straighten" msgid="5217801513491493491">"Suorista"</string>
-    <string name="temperature" msgid="1607987938521534517">"Lämpö"</string>
-    <string name="tint" msgid="154435943863418434">"Sävytys"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjetti"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Leikkaa kuvaa vetämällä merkkejä"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Piirrä valokuvaan siirtämällä osoitin sen päälle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Käännä kuva vetämällä"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Poista punasilmäisyys napautt. silmiä"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Käännä valokuvaa vetämällä"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Suorista valokuva vetämällä"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Valotustehosteet"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Väritehosteet"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Taiteelliset tehosteet"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Korjaa"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Kumoa"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Toista"</string>
-</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 7ec6ecf..528f885 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Kierrä myötäpäivään"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Kohdetta ei löytynyt."</string>
     <string name="edit" msgid="1502273844748580847">"Muokkaa"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Yksinkert. muokkaus"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Käsitellään välimuistipyyntöjä"</string>
     <string name="caching_label" msgid="4521059045896269095">"Vie välimuist."</string>
     <string name="crop_action" msgid="3427470284074377001">"Rajaa"</string>
     <string name="trim_action" msgid="703098114452883524">"Leikkaa"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Mykistä"</string>
     <string name="set_as" msgid="3636764710790507868">"Aseta"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Videon mykistys epäonnistui."</string>
     <string name="video_err" msgid="7003051631792271009">"Videon toistaminen epäonnistui."</string>
     <string name="group_by_location" msgid="316641628989023253">"Sijainnin mukaan"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Ajan mukaan"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Autom."</string>
     <string name="flash_on" msgid="7891556231891837284">"Salama käyt."</string>
     <string name="flash_off" msgid="1445443413822680010">"Ei salamaa"</string>
+    <string name="unknown" msgid="3506693015896912952">"Tuntematon"</string>
     <string name="ffx_original" msgid="372686331501281474">"Alkuperäinen"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vanha"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Pika"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Ei ulkoista tallennustilaa"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filminäkymä"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Ruudukkonäkymä"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Koko ruudun näkymä"</string>
     <string name="trimming" msgid="9122385768369143997">"Leikataan"</string>
+    <string name="muting" msgid="5094925919589915324">"Mykistetään"</string>
     <string name="please_wait" msgid="7296066089146487366">"Odota"</string>
-    <string name="save_into" msgid="4960537214388766062">"Tallennetaan lyhennettyä videota albumiin"</string>
+    <string name="save_into" msgid="9155488424829609229">"Tallennetaan videota albumiin <xliff:g id="ALBUM_NAME">%1$s</xliff:g>..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"Ei voi leikata: kohdevideo on liian lyhyt"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panoraamaa hahmonnetaan"</string>
     <string name="save" msgid="613976532235060516">"Tallenna"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skannataan sisältöä..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d kohdetta skannattu"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d kohde skannattu"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d kohdetta skannattu"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Järjestellään..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skannaus valmis"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Tuodaan..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Tällä laitteella ei ole tuotavaa sisältöä."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"MTP-laitetta ei ole yhdistetty"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kameravirhe"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Ei saada yhteyttä kameraan."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera on poistettu käytöstä suojauskäytäntöjen vuoksi."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Odota…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Ota USB-tallennustila käyttöön ennen kameran käyttöä."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Aseta SD-kortti ennen kameran käyttämistä."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Valmistellaan USB-tilaa..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Valmistellaan SD-korttia…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USB-tallennuslaitetta ei voi käyttää."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SD-korttia ei voi käyttää."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"PERUUTA"</string>
+    <string name="review_ok" msgid="1156261588693116433">"VALMIS"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Intervallikuvauksen tallennus"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Valitse kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Takaisin"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Etupuoli"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Tallennussijainti"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"SIJAINTI"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Ajastin"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekunti"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sekuntia"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Ajastimen ääni"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Pois käytöstä"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Käytössä"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videon laatu"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Korkea"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Alhainen"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervallikuvaus"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kameran asetukset"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokameran asetukset"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Kuvan koko"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapikseliä"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapikseliä"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapikseliä"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapikseliä"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapikseliä"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapiks."</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapikseli"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Tarkennustila"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automaattinen"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Loputon"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMAATTINEN"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"LOPUTON"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Salaman tila"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"SALAMAN TILA"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automaattinen"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Käytössä"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Pois käytöstä"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMAATTINEN SALAMA"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"SALAMA PÄÄLLÄ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"SALAMA POIS"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Valkotasapaino"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"VALKOTASAPAINO"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automaattinen"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Hehkulamppuvalo"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Päivänvalo"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Loisteputkivalo"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Pilvinen"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMAATTINEN"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"HEHKULAMPPUVALO"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"PÄIVÄNVALO"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"LOISTEPUTKIVALO"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"PILVINEN"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Kuvaustila"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automaattinen"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Toiminta"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Yö"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Auringonlasku"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Juhlat"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"EI MITÄÄN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"TOIMINTA"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"YÖ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"AURINGONLASKU"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"JUHLAT"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"AJASTIN"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"AJASTIN EI OLE KÄYTÖSSÄ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNTI"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNTIA"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNTIA"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNTIA"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Ei valittavissa kuvaustilassa."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Valotus"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"VALOTUS"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ETUKAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"TAKAKAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB-tallennustilasi on vähissä. Vaihda laatuasetusta tai poista kuvia tai muita tiedostoja."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD-korttisi tila on vähissä. Vaihda laatuasetusta tai poista kuvia tai muita tiedostoja."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Videon koko on suurin mahdollinen."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Liian nopea"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Valmistellaan panoraamaa"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panoraaman tallennus epäonnistui."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoraama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panoraamakuvaus"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Odotetaan edellistä panoraamaa"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Tallennetaan…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panoraamaa hahmonnetaan"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Tarkenna koskettamalla."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Tehosteet"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ei mitään"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Litistetty"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Isot silmät"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Iso suu"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Pieni suu"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Iso nenä"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Pienet silmät"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Avaruus"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Auringonlasku"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Oma videosi"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Aseta laite alustalle."\n"Astu hetkeksi pois kuvasta."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Ota kuva tallennuksen aikana koskettamalla ruutua."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Videon tallennus on alkanut."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Videon tallennus on pysäytetty."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Videon kuvakaappausta ei voi käyttää erikoistehosteiden kanssa."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Tyhjennä tehosteet"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"HAUSKAT KASVOT"</string>
+    <string name="effect_background" msgid="6579360207378171022">"TAUSTA"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Laukaisupainike"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Valikkopainike"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Viimeisin valokuva"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Etu- ja takakamerakytkin"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kamera-, video- tai panoraamavalitsin"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Lisää asetuksia"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Sulje asetukset"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoomauksen hallinta"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Vähennä %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Kasvata %1$s-arvoa"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s -valintaruutu"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Vaihda kuvatilaan"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Vaihda videotilaan"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Vaihda panoraamatilaan"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Vaihda uuteen panoraamaan"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Peruuta"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Tarkistus valmis"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Kuvaa uudelleen"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Katso video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Keskeytä video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Lataa video uudelleen"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Videosoittimen aikapalkki"</string>
+    <string name="capital_on" msgid="5491353494964003567">"KÄYTÖSSÄ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"POIS KÄYTÖSTÄ"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Pois käytöstä"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunti"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuutti"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuuttia"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 tunti"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 tuntia"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 tuntia"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekuntia"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuuttia"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"tuntia"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Valmis"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Aseta aikaväli"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Intervallikuvaus ei ole käytössä. Ota ominaisuus käyttöön, jos haluat määrittää aikavälin."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Ajastin ei ole käytössä. Ota se käyttöön, jos haluat ottaa kuvan ajastimella."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Aseta kesto sekunteina"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Kuva otetaan, kun ajastin saavuttaa nollan"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Muistetaanko valokuvien sijainnit?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Merkitse valokuviin ja videoihin niiden kuvauspaikat."\n\n"Muut sovellukset voivat käyttää näitä tietoja tallennettujen kuviesi yhteydessä."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ei kiitos"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Kyllä"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Haku"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Kuvat"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumit"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"LISÄÄ VAIHTOEHTOJA"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"ASETUKSET"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d valokuva"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d valokuvaa"</item>
+  </plurals>
 </resources>
diff --git a/res/values-fr/filtershow_strings.xml b/res/values-fr/filtershow_strings.xml
index 3a6906f..8face03 100644
--- a/res/values-fr/filtershow_strings.xml
+++ b/res/values-fr/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Impossible de charger l\'image."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Définition du fond d\'écran en cours…"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Impossible de télécharger la photo. Réseau indisponible."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Contours"</string>
-    <string name="done" msgid="3112344807927554662">"OK"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Annuler"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Rétablir"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Afficher historique"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Masquer l\'historique"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Afficher état image"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Masquer état image"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Afficher les effets appliqués"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Masquer les effets appliqués"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Paramètres"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Certaines modifications apportées à l\'image n\'ont pas été enregistrées."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Souhaitez-vous enregistrer les modifications avant de fermer le programme ?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Enregistrer et quitter"</string>
+    <string name="exit" msgid="242642957038770113">"Quitter"</string>
     <string name="history" msgid="455767361472692409">"Historique"</string>
     <string name="reset" msgid="9013181350779592937">"Réinitialiser"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"État actuel de l\'image"</string>
+    <string name="imageState" msgid="8632586742752891968">"Effets appliqués"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparer"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Appliquer"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Réinitialiser"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Aucun"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fixe"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Petite planète"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposition"</string>
     <string name="sharpness" msgid="6463103068318055412">"Netteté"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Coloration auto"</string>
     <string name="hue" msgid="6231252147971086030">"Teinte"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombres"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Reflets"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Courbes"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetage"</string>
     <string name="redeye" msgid="4508883127049472069">"Yeux rouges"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Dessiner"</string>
     <string name="straighten" msgid="26025591664983528">"Redresser"</string>
     <string name="crop" msgid="5781263790107850771">"Rogner"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotation"</string>
     <string name="mirror" msgid="5482518108154883096">"Miroir"</string>
+    <string name="negative" msgid="6998313764388022201">"Négatif"</string>
     <string name="none" msgid="6633966646410296520">"Aucun"</string>
+    <string name="edge" msgid="7036064886242147551">"Bords"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Réduction"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RVB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rouge"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Vert"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Bleu"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Style"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Taille"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Couleur"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Lignes"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marqueur"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Éclaboussures"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Effacer"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Sélection couleur personnalisée"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Sélectionner couleur"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Sélectionner la taille"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Image originale"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Résultat"</string>
 </resources>
diff --git a/res/values-fr/photoeditor_strings.xml b/res/values-fr/photoeditor_strings.xml
deleted file mode 100644
index 1d3b324..0000000
--- a/res/values-fr/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Impossible de charger la photo."</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Imposs. enregistrer photo retouchée."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Photo retouchée enregistrée dans <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Annuler modifications non enregistrées ?"</string>
-    <string name="yes" msgid="5402582493291792293">"Oui"</string>
-    <string name="save" msgid="5516670392524294967">"ENREG."</string>
-    <string name="autofix" msgid="1663414996270538748">"Correction auto"</string>
-    <string name="crop" msgid="7598378507763334041">"Rogner"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Traitement croisé"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentaire"</string>
-    <string name="doodle" msgid="1686409894518940990">"Gribouillage"</string>
-    <string name="duotone" msgid="8145893940788467106">"Deux tons"</string>
-    <string name="facelift" msgid="6205748523156172637">"Halo visage"</string>
-    <string name="facetan" msgid="4412831806626044111">"Bronzage visage"</string>
-    <string name="filllight" msgid="2644989991700022526">"Éclairage d\'appoint"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Objectif fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Retourner"</string>
-    <string name="grain" msgid="7487585304579789098">"Grain"</string>
-    <string name="grayscale" msgid="7641563843060945228">"N&amp;B"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Noir et blanc"</string>
-    <string name="highlight" msgid="3902653944386623972">"Reflets"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomographie"</string>
-    <string name="negative" msgid="1985508917342811252">"Négatif"</string>
-    <string name="posterize" msgid="4139212359561383385">"Postérisation"</string>
-    <string name="redeye" msgid="4958448806369928239">"Yeux rouges"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotation"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Ombres"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Netteté"</string>
-    <string name="straighten" msgid="5217801513491493491">"Redresser"</string>
-    <string name="temperature" msgid="1607987938521534517">"Chaud"</string>
-    <string name="tint" msgid="154435943863418434">"Coloration"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignetage"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Faire glisser les marqueurs pour rogner la photo"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Dessiner sur la photo pour gribouiller"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Faire glisser la photo pour la retourner"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Appuyer sur yeux rouges pour les effacer"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Faire glisser la photo pour la faire pivoter"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Faire glisser la photo pour la redresser"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Effets d\'exposition"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Effets de couleur"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Effets artistiques"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Retoucher"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Annuler"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Rétablir"</string>
-</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 58d7d1f..b556466 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Faire pivoter à droite"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Impossible de trouver l\'élément."</string>
     <string name="edit" msgid="1502273844748580847">"Retoucher"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Modification simple"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Demandes de mise en cache en cours de traitement"</string>
     <string name="caching_label" msgid="4521059045896269095">"Mise en cache…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Rogner"</string>
     <string name="trim_action" msgid="703098114452883524">"Rogner"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Couper le son"</string>
     <string name="set_as" msgid="3636764710790507868">"Définir comme"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Impossible de couper le son."</string>
     <string name="video_err" msgid="7003051631792271009">"Impossible de lire la vidéo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Par emplacement"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Par date"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automatique"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash déclenché"</string>
     <string name="flash_off" msgid="1445443413822680010">"Flash désactivé"</string>
+    <string name="unknown" msgid="3506693015896912952">"Inconnue"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instantané"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Aucune mémoire de stockage externe disponible."</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Pellicule"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Grille"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Affichage plein écran"</string>
     <string name="trimming" msgid="9122385768369143997">"Découpe en cours"</string>
+    <string name="muting" msgid="5094925919589915324">"Coupure du son"</string>
     <string name="please_wait" msgid="7296066089146487366">"Veuillez patienter."</string>
-    <string name="save_into" msgid="4960537214388766062">"Enregistrement de la vidéo coupée dans l\'album"</string>
+    <string name="save_into" msgid="9155488424829609229">"Enregistrement de la vidéo dans l\'album \"<xliff:g id="ALBUM_NAME">%1$s</xliff:g>\" en cours…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Découpe impossible : la vidéo cible est trop courte."</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Rendu de la vue panoramique en cours…"</string>
     <string name="save" msgid="613976532235060516">"Enregistrer"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Analyse du contenu en cours..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d éléments analysés"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d élément analysé"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d éléments analysés"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Tri en cours..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Analyse terminée."</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importation en cours..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Aucun contenu n\'est disponible pour une importation sur cet appareil."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Aucun appareil MTP n\'est connecté."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Erreur de l\'appareil photo."</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Impossible d\'établir une connexion avec l\'appareil photo."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"L\'appareil photo a été désactivé en raison des règles de sécurité."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Appareil photo"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Caméra"</string>
+    <string name="wait" msgid="8600187532323801552">"Veuillez patienter..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Veuillez installer une mémoire de stockage USB avant d\'utiliser l\'appareil photo."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Veuillez insérer une carte SD avant d\'utiliser l\'appareil photo."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Préparation mémoire de stockage USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Préparation de la carte SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Impossible d\'accéder à la mémoire de stockage USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Impossible d\'accéder à la carte SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ANNULER"</string>
+    <string name="review_ok" msgid="1156261588693116433">"OK"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Enregistrement mode time lapse"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Sélectionner caméra"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Arrière"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Avant"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Enregist. position"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"POSITION"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Compte à rebours"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 seconde"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d secondes"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Compte à rebours sonore"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Désactivé"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Activé"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualité vidéo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Élevée"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Faible"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervalle de temps"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Paramètres appareil photo"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Mode Caméra"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Taille d\'image"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 mégapixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 mégapixels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Mpx (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 M pixels"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Mise au point"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infini"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATIQUE"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINI"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Mode Flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODE FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatique"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Activé"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Désactivé"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTOMATIQUE"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ACTIVÉ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DÉSACTIVÉ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balance des blancs"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANCE DES BLANCS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatique"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescent"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Lumière du jour"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescent"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nuageux"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATIQUE"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENT"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUMIÈRE DU JOUR"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENT"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUAGEUX"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Mode Scène"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatique"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Action"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nuit"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Coucher de soleil"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fête"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"AUCUN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACTION"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NUIT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"COUCHER DE SOLEIL"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FÊTE"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"COMPTE À REBOURS"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"MINUTEUR DÉSACTIVÉ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SECONDE"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SECONDES"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SECONDES"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SECONDES"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Ce paramètre ne peut pas être sélectionné en mode Scène."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposition"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSITION"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CAMÉRA FRONTALE"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CAMÉRA ARRIÈRE"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"La mémoire de stockage USB est pleine. Définissez la qualité sur une valeur plus basse ou supprimez des images ou d\'autres types de fichiers."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Votre carte SD est pleine. Modifiez le paramètre de qualité ou supprimez des images ou des fichiers."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Taille maximale atteinte."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Trop rapide"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Préparation vue panoramique..."</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Impossible d\'enregistrer le panoramique."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoramique"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Capture vue panoramique…"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Vue panoramique précédente en attente"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Enreg…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Rendu de vue panoramique…"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Appuyez pour mise au point."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effets"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Aucun"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Compresser"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Grands yeux"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Grande bouche"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Petite bouche"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Gros nez"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Petits yeux"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Dans l\'espace"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Coucher soleil"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Votre vidéo"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Posez votre appareil."\n"Sortez du cadre quelques instants."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Appuyez pour prendre une photo pendant l\'enregistrement"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"L\'enregistrement vidéo a commencé."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"L\'enregistrement vidéo s\'est arrêté."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Instantané vidéo désactivé en cas d\'activation des effets spéciaux."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Effacer les effets"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"DRÔLES DE TÊTES"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ARRIÈRE-PLAN"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Bouton de l\'obturateur"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Bouton \"Menu\""</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Photo la plus récente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Interrupteur des caméras avant et arrière"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Sélecteur du mode Photo, Vidéo ou Panoramique"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Plus de paramètres"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Fermer les paramètres"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Contrôle du zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Diminuer %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Augmenter %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Case à cocher %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Mode Appareil photo"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Mode vidéo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Mode panoramique"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Passer au nouveau mode panoramique"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Examen – Annuler"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Examen – OK"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Examiner la deuxième prise de la photo"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Lire la vidéo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Interrompre la vidéo"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Revoir la vidéo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barre chronologique du lecteur vidéo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ACTIVÉ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DÉSACTIVÉ"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Désactivé"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"1 demi-seconde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 seconde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1 seconde et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2 secondes et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 secondes"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"1 demi-minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1 minute et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2 minutes et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"1 demi-heure"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 heure"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1 heure et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2 heures et demie"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 heures"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 heures"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"secondes"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutes"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"heures"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"OK"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Définir l\'intervalle de temps"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"La fonctionnalité Time Lapse est désactivée. Veuillez l\'activer pour définir un intervalle."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"La fonctionnalité de compte à rebours est désactivée. Activez-la pour effectuer un compte à rebours avant de prendre une photo."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Définir la durée en secondes"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Compte à rebours avant la photo"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Se souvenir du lieu des photos ?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Ajoutez des tags à vos photos et à vos vidéos pour identifier l\'endroit de la prise de vue."\n\n"D\'autres applications peuvent accéder à ces informations, ainsi qu\'aux images enregistrées."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Non, merci"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Oui"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Appareil photo"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Recherche"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Photos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"PLUS D\'OPTIONS"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"PARAMÈTRES"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d photo"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d photos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-hi/filtershow_strings.xml b/res/values-hi/filtershow_strings.xml
index bca9006..2516a00 100644
--- a/res/values-hi/filtershow_strings.xml
+++ b/res/values-hi/filtershow_strings.xml
@@ -20,23 +20,26 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"चित्र लोड नहीं हो सकता!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"वॉलपेपर सेट हो रहा है"</string>
+    <string name="download_failure" msgid="5923323939788582895">"फ़ोटो डाउनलोड नहीं किया जा सका. नेटवर्क अनुपलब्ध है."</string>
     <string name="original" msgid="3524493791230430897">"मूल"</string>
     <string name="borders" msgid="2067345080568684614">"बॉर्डर"</string>
-    <string name="done" msgid="3112344807927554662">"पूर्ण"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"पूर्ववत करें"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"फिर से करें"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"इतिहास दिखाएं"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"इतिहास छिपाएं"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"चित्र स्थिति दिखाएं"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"चित्र स्थिति छिपाएं"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"लागू प्रभाव दिखाएं"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"लागू प्रभाव छिपाएं"</string>
     <string name="menu_settings" msgid="6428291655769260831">"सेटिंग"</string>
+    <string name="unsaved" msgid="8704442449002374375">"इस चित्र में न सहेजे गए बदलाव हैं."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"क्या आप बाहर निकलने के पहले सहेजना चाहते हैं?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"सहेजें और बाहर निकलें"</string>
+    <string name="exit" msgid="242642957038770113">"बाहर निकलें"</string>
     <string name="history" msgid="455767361472692409">"इतिहास"</string>
     <string name="reset" msgid="9013181350779592937">"रीसेट करें"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"वर्तमान चित्र स्थिति"</string>
+    <string name="imageState" msgid="8632586742752891968">"लागू किए गए प्रभाव"</string>
     <string name="compare_original" msgid="8140838959007796977">"तुलना करें"</string>
-    <string name="apply_effect" msgid="1218288221200568947">"लागू करें"</string>
+    <string name="apply_effect" msgid="1218288221200568947">"*"</string>
     <string name="reset_effect" msgid="7712605581024929564">"रीसेट करें"</string>
     <string name="aspect" msgid="4025244950820813059">"पहलू"</string>
     <string name="aspect1to1_effect" msgid="1159104543795779123">"1:1"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"कुछ नहीं"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"निर्धारित"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"छोटा ग्रह"</string>
     <string name="exposure" msgid="6526397045949374905">"एक्सपोज़र"</string>
     <string name="sharpness" msgid="6463103068318055412">"शार्पनेस"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"ऑटोकलर"</string>
     <string name="hue" msgid="6231252147971086030">"ह्यू"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"छाया"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"हाइलाइट"</string>
     <string name="curvesRGB" msgid="915010781090477550">"वक्र"</string>
     <string name="vignette" msgid="934721068851885390">"विनेट"</string>
     <string name="redeye" msgid="4508883127049472069">"रेड आई"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"रेखांकन"</string>
     <string name="straighten" msgid="26025591664983528">"सीधा करें"</string>
     <string name="crop" msgid="5781263790107850771">"काट-छांट करें"</string>
     <string name="rotate" msgid="2796802553793795371">"घुमाएं"</string>
     <string name="mirror" msgid="5482518108154883096">"दर्पण"</string>
+    <string name="negative" msgid="6998313764388022201">"नेगेटिव"</string>
     <string name="none" msgid="6633966646410296520">"कुछ नहीं"</string>
+    <string name="edge" msgid="7036064886242147551">"किनारे"</string>
+    <string name="kmeans" msgid="1630263230946107457">"वारहोल"</string>
+    <string name="downsample" msgid="3552938534146980104">"डाउनसेंपल"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"लाल"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"हरा"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"नीला"</string>
+    <string name="draw_style" msgid="2036125061987325389">"शैली"</string>
+    <string name="draw_size" msgid="4360005386104151209">"आकार"</string>
+    <string name="draw_color" msgid="2119030386987211193">"रंग"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"रेखाएं"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"मार्कर"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"स्पैटर"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"साफ़ करें"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"कस्टम रंग चुनें"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"रंग चुनें"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"आकार चुनें"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"ठीक"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"मूल"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"परिणाम"</string>
 </resources>
diff --git a/res/values-hi/photoeditor_strings.xml b/res/values-hi/photoeditor_strings.xml
deleted file mode 100644
index 7c7a908..0000000
--- a/res/values-hi/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"फ़ोटो लोड नहीं किया जा सका"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"संपादित फ़ोटो को सहेजा नहीं जा सका"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"संपादित फ़ोटो को <xliff:g id="FOLDER_NAME">%s</xliff:g> में सहेजा गया"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"न सहेजे गए परिवर्तनों को छोड़ें?"</string>
-    <string name="yes" msgid="5402582493291792293">"हां"</string>
-    <string name="save" msgid="5516670392524294967">"सहेजें"</string>
-    <string name="autofix" msgid="1663414996270538748">"स्‍वत: समायोजन"</string>
-    <string name="crop" msgid="7598378507763334041">"काट-छांट करें"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"क्रॉस-प्रोसेस"</string>
-    <string name="documentary" msgid="50396326708699797">"वृत्तचित्र"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"ड्‍यु-टोन"</string>
-    <string name="facelift" msgid="6205748523156172637">"चेहरे की चमक"</string>
-    <string name="facetan" msgid="4412831806626044111">"चेहरे का रंग गहरा"</string>
-    <string name="filllight" msgid="2644989991700022526">"प्रकाश भरें"</string>
-    <string name="fisheye" msgid="6037488646928998921">"फ़िशआई"</string>
-    <string name="flip" msgid="2357692401826287480">"फ़्लिप करें"</string>
-    <string name="grain" msgid="7487585304579789098">"फ़िल्म ग्रेन"</string>
-    <string name="grayscale" msgid="7641563843060945228">"श्याम और श्वेत"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"श्वेत-श्याम"</string>
-    <string name="highlight" msgid="3902653944386623972">"हाइलाइट"</string>
-    <string name="lomoish" msgid="1270032154357186736">"लोमो"</string>
-    <string name="negative" msgid="1985508917342811252">"नकारात्मक"</string>
-    <string name="posterize" msgid="4139212359561383385">"पोस्टराइज़"</string>
-    <string name="redeye" msgid="4958448806369928239">"रेड आई"</string>
-    <string name="rotate" msgid="6607597269792373083">"घुमाएं"</string>
-    <string name="saturation" msgid="8621322012271169931">"संतृप्तता"</string>
-    <string name="sepia" msgid="7978093531824705601">"सेपिया"</string>
-    <string name="shadow" msgid="8235188588101973090">"छाया"</string>
-    <string name="sharpen" msgid="8449662378104403230">"शार्प करें"</string>
-    <string name="straighten" msgid="5217801513491493491">"सीधा करें"</string>
-    <string name="temperature" msgid="1607987938521534517">"उत्‍साह"</string>
-    <string name="tint" msgid="154435943863418434">"टिंट"</string>
-    <string name="vignette" msgid="7648125924662648282">"विनेट"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"फ़ोटो की काट-छांट करने के लि‍ए मार्कर को खींचें"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"doodle के लिए फ़ोटो पर आरेखित करें"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"फ़ोटो को फ़्लिप करने के लिए उसे खींचें"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"रेड आई निकालने के लिए उन पर टैप करें"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"घुमाने के लिए फ़ोटो को खींचें"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"सीधा करने के लिए फ़ोटो को खींचें"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"एक्सपोज़र प्रभाव"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"रंग प्रभाव"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"कलात्मक प्रभाव"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"फ़िक्स करें"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"पूर्ववत करें"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"फिर से करें"</string>
-</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 2bf3718..e6325a3 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -38,7 +38,7 @@
     <string name="saving_image" msgid="7270334453636349407">"चित्र सहेज रहा है…"</string>
     <string name="filtershow_saving_image" msgid="6659463980581993016">"चित्र <xliff:g id="ALBUM_NAME">%1$s</xliff:g> में सहेजा जा रहा है …"</string>
     <string name="save_error" msgid="6857408774183654970">"काट-छांट की गई चित्र को नहीं सहेज सका."</string>
-    <string name="crop_label" msgid="521114301871349328">"चित्र काटें"</string>
+    <string name="crop_label" msgid="521114301871349328">"चित्र काट-छांट करें"</string>
     <string name="trim_label" msgid="274203231381209979">"वीडियो ट्रिम करें"</string>
     <string name="select_image" msgid="7841406150484742140">"फ़ोटो को चुनें"</string>
     <string name="select_video" msgid="4859510992798615076">"वीडियो को चुनें"</string>
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"दाएं घुमाएं"</string>
     <string name="no_such_item" msgid="5315144556325243400">"आइटम नहीं ढूंढा जा सका."</string>
     <string name="edit" msgid="1502273844748580847">"संपादित करें"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"सरल संपादन"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"संचय अनुरोध संसाधित कर रहा है"</string>
     <string name="caching_label" msgid="4521059045896269095">"संचय कर रहा है..."</string>
     <string name="crop_action" msgid="3427470284074377001">"काट-छांट करें"</string>
     <string name="trim_action" msgid="703098114452883524">"ट्रिम करें"</string>
+    <string name="mute_action" msgid="5296241754753306251">"म्यूट करें"</string>
     <string name="set_as" msgid="3636764710790507868">"इस रूप में सेट करें"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"वीडियो म्यूट नहीं हो सकता."</string>
     <string name="video_err" msgid="7003051631792271009">"वीडियो नहीं चल सकता."</string>
     <string name="group_by_location" msgid="316641628989023253">"स्थान द्वारा"</string>
     <string name="group_by_time" msgid="9046168567717963573">"समय द्वारा"</string>
@@ -104,14 +107,14 @@
     <string name="no_location" msgid="4043624857489331676">"कोई स्थान नहीं"</string>
     <string name="no_connectivity" msgid="7164037617297293668">"नेटवर्क समस्‍याओं के कारण कुछ स्‍थानों को पहचाना नहीं जा सका."</string>
     <string name="sync_album_error" msgid="1020688062900977530">"इस एल्बम के फ़ोटो डाउनलोड नहीं किए जा सके. बाद में पुन: प्रयास करें."</string>
-    <string name="show_images_only" msgid="7263218480867672653">"केवल छवियां"</string>
+    <string name="show_images_only" msgid="7263218480867672653">"केवल चित्र"</string>
     <string name="show_videos_only" msgid="3850394623678871697">"केवल वीडियो"</string>
-    <string name="show_all" msgid="6963292714584735149">"छवियां और वीडियो"</string>
+    <string name="show_all" msgid="6963292714584735149">"चित्र और वीडियो"</string>
     <string name="appwidget_title" msgid="6410561146863700411">"फ़ोटो गैलरी"</string>
     <string name="appwidget_empty_text" msgid="1228925628357366957">"कोई फ़ोटो नहीं."</string>
     <string name="crop_saved" msgid="1595985909779105158">"काट-छांट की गई चित्र को <xliff:g id="FOLDER_NAME">%s</xliff:g> में सहेजा गया."</string>
     <string name="no_albums_alert" msgid="4111744447491690896">"कोई एल्‍बम उपलब्‍ध नहीं."</string>
-    <string name="empty_album" msgid="4542880442593595494">"O छवियां/वीडियो उपलब्‍ध."</string>
+    <string name="empty_album" msgid="4542880442593595494">"O चित्र/वीडियो उपलब्‍ध."</string>
     <string name="picasa_posts" msgid="1497721615718760613">"पोस्ट"</string>
     <string name="make_available_offline" msgid="5157950985488297112">"ऑफ़लाइन उपलब्ध कराएं"</string>
     <string name="sync_picasa_albums" msgid="8522572542111169872">"रीफ्रेश करें"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"स्वत:"</string>
     <string name="flash_on" msgid="7891556231891837284">"फ़्लैश चलाया गया"</string>
     <string name="flash_off" msgid="1445443413822680010">"कोई फ़्लैश नहीं"</string>
+    <string name="unknown" msgid="3506693015896912952">"अज्ञात"</string>
     <string name="ffx_original" msgid="372686331501281474">"मूल"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"विंटेज"</string>
     <string name="ffx_instant" msgid="726968618715691987">"झटपट"</string>
@@ -173,7 +177,7 @@
     <string name="widget_type_album" msgid="6013045393140135468">"कोई एल्बम चुनें"</string>
     <string name="widget_type_shuffle" msgid="8594622705019763768">"सभी छवियों का क्रम बदलें"</string>
     <string name="widget_type_photo" msgid="6267065337367795355">"कोई चित्र चुनें"</string>
-    <string name="widget_type" msgid="1364653978966343448">"छवियां चुनें"</string>
+    <string name="widget_type" msgid="1364653978966343448">"चित्र चुनें"</string>
     <string name="slideshow_dream_name" msgid="6915963319933437083">"स्लाइडशो"</string>
     <string name="albums" msgid="7320787705180057947">"एल्बम"</string>
     <string name="times" msgid="2023033894889499219">"इतने बार"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"कोई बाहरी संग्रहण उपलब्ध नहीं है"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"फ़िल्मस्ट्रिप दृश्य"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"ग्रिड दृश्य"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"पूर्णस्क्रीन दृश्य"</string>
     <string name="trimming" msgid="9122385768369143997">"ट्रिम कर रहा है"</string>
+    <string name="muting" msgid="5094925919589915324">"म्‍यूट हो रहा है"</string>
     <string name="please_wait" msgid="7296066089146487366">"कृपया प्रतीक्षा करें"</string>
-    <string name="save_into" msgid="4960537214388766062">"ट्रिम किया गया वीडियो एल्बम में सहेजा जा रहा है :"</string>
+    <string name="save_into" msgid="9155488424829609229">"वीडियो को <xliff:g id="ALBUM_NAME">%1$s</xliff:g> में सहेजा जा रहा है …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"ट्रिम नहीं कर सकते : लक्ष्य वीडियो बहुत छोटा है"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"पैनोरामा रेंडर किया जा रहा है"</string>
     <string name="save" msgid="613976532235060516">"सहेजें"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"सामग्री स्कैन हो रही है..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d आइटम स्कैन किए गए"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d आइटम स्कैन किया गया"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d आइटम स्कैन किए गए"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"क्रमित हो रही है..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"स्कैन करना पूर्ण"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"आयात हो रही है..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"आयात करने के लिए इस उपकरण पर कोई सामग्री उपलब्ध नहीं है."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"कोई MTP उपकरण कनेक्ट नहीं है"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"कैमरा त्रुटि"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"कैमरे से कनेक्‍ट नहीं कर सकता."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"सुरक्षा नीतियों के कारण कैमरा अक्षम कर दिया गया है."</string>
+    <string name="camera_label" msgid="6346560772074764302">"कैमरा"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"कैमकॉर्डर"</string>
+    <string name="wait" msgid="8600187532323801552">"कृपया प्रतीक्षा करें..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"कैमरे का उपयोग करने से पहले USB संग्रहण माउंट करें."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"कैमरे का उपयोग करने से पहले SD कार्ड डालें."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB संग्रहण तैयार कर रहा है…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD कार्ड तैयार कर रहा है…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USB संग्रहण में नहीं पहुंच सका."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SD कार्ड में नहीं पहुंच सका."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"रद्द करें"</string>
+    <string name="review_ok" msgid="1156261588693116433">"पूर्ण"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"समय समाप्ति रिकॉर्डिंग"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"कैमरा चुनें"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"वापस जाएं"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"सामने"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"संग्रह स्थान"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"स्थान"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"काउंटडाउन टाइमर"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 सेकंड"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d सेकंड"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"उल्टी गिनती के समय बीप"</string>
+    <string name="setting_off" msgid="4480039384202951946">"बंद"</string>
+    <string name="setting_on" msgid="8602246224465348901">"चालू"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"वीडियो गुणवत्ता"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"उच्च"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"निम्न"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"समय अंतराल"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"कैमरा सेटिंग"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"कैमकॉर्डर सेटिंग"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"चित्र आकार"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M पिक्सेल (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M पिक्सेल"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"फ़ोकस मोड"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"स्वत:"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"अनंत"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"मैक्रो"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"स्वत:"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"अनंत"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"मैक्रो"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"फ़्लैश मोड"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"फ़्लैश मोड"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"स्वत:"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"चालू"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"बंद"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"फ़्लैश स्वत:"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"फ़्लैश चालू"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"फ़्लैश बंद"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"श्वेत संतुलन"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"श्वेत संतुलन"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"स्वत:"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"अत्यधिक चमकीला"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"दिन का प्रकाश"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"फ़्लोरेसेंट"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"बदली"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"स्वत:"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"अत्यधिक चमकीला"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"दिन का प्रकाश"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"फ़्लोरेसेंट"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"बादलयुक्त"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"दृश्य मोड"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"स्वत:"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"कार्यवाही"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"रात्रि"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"सूर्यास्त"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"पार्टी"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"कोई नहीं"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"कार्यवाही"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"रात"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"सूर्यास्त"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"पार्टी"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"काउंटडाउन टाइमर"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"टाइमर बंद"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 सेकंड"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 सेकंड"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 सेकंड"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 सेकंड"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"दृश्य मोड में चयन योग्य नहीं."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"एक्स्पोजर"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"एक्सपोज़र"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"सामने का कैमरा"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"पीछे का कैमरा"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"ठीक"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"आपके USB संग्रहण में स्थान कम है. गुणवत्ता सेटिंग बदलें या कुछ चित्र या अन्य फ़ाइलें हटाएं."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"आपके SD कार्ड में स्थान कम है. गुणवत्ता सेटिंग बदलें या कुछ चित्र या अन्य फ़ाइलें हटाएं."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"आकार सीमा पहुंची."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"बहुत तेज़"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"पैनोरामा तैयार हो रहा है"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"पैनोरामा नहीं सहेज सका."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"पैनोरामा"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"पैनोरामा कैप्चर हो रहा है"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"पिछले पैनोरामा की प्रतीक्षा की जा रही है"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"सहेजा जा रहा है..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"पैनोरामा रेंडर हो रहा है"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"फ़ोकस करने हेतु स्‍पर्श करें."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"प्रभाव"</string>
+    <string name="effect_none" msgid="3601545724573307541">"कोई नहीं"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"पिचका हुआ"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"बड़ी आंखें"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"बड़ा मुंह"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"छोटा मुंह"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"बड़ी नाक"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"छोटी आंखें"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"अंतरिक्ष में"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"सूर्यास्त"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"आपका वीडियो"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"अपने उपकरण को सेट करें."\n"कुछ समय के लिए दृश्य से बाहर निकलें."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"रिकॉर्डिंग के दौरान फ़ोटो लेने के लिए स्‍पर्श करें."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"वीडियो रिकॉर्डिंग प्रारंभ हो गई है."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"वीडियो रिकॉर्डिंग रुक गई है."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"विशेष प्रभावों के चालू होने पर वीडियो स्‍नेपशॉट अक्षम हो जाता है."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"प्रभाव साफ़ करें"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"मज़ाकिया चेहरे"</string>
+    <string name="effect_background" msgid="6579360207378171022">"पृष्ठभूमि"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"शटर बटन"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"मेनू बटन"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"हाल ही का फ़ोटो"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"अगला और पिछला कैमरा स्‍विच"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"कैमरा, वीडियो या पैनोरामा चयनकर्ता"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"अधिक सेटिंग नियंत्रण"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"सेटिंग नियंत्रण बंद करें"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"ज़ूम नियंत्रण"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"%1$s घटाएं"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"%1$s बढ़ाएं"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s चेक बॉक्स"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"फ़ोटो पर स्‍विच करें"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"वीडियो पर स्विच करें"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"पैनोरामा पर स्विच करें"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"नए पैनोरामा पर स्विच करें"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"समीक्षा रद्द कर दी गई"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"समीक्षा पूर्ण"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"समीक्षा रीटेक"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"वीडियो चलाएं"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"वीडियो पॉज़ करें"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"वीडियो पुन: लोड करें"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"वीडियो प्लेयर समय बार"</string>
+    <string name="capital_on" msgid="5491353494964003567">"चालू"</string>
+    <string name="capital_off" msgid="7231052688467970897">"बंद"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"बंद"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 सेकंड"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 मिनट"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 घंटा"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 घंटा"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 घंटा"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 घंटे"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 घंटे"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"सेकंड"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"मिनट"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"घंटे"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"पूर्ण"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"समय अंतराल सेट करें"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"समय अंतराल सुविधा बंद है. समय अंतराल सेट करने के लिए इसे चालू करें."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"काउंटडाउन टाइमर बंद है. चित्र लेने से पहले उल्‍टी गिनती करने के लिए इसे चालू करें."</string>
+    <string name="set_duration" msgid="5578035312407161304">"अवधि को सेकंड में सेट करें"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"फ़ोटो लेने के लिए उल्टी गिनती कर रहा है"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"फ़ोटो के स्थान याद हैं?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"अपने फ़ोटो और वीडियो को उन स्थानों के साथ टैग करें जहां वे लिए गए हैं."\n\n"अन्य एप्लिकेशन आपके सहेजे गए चित्रों सहित इस जानकारी का उपयोग कर सकते हैं."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"नहीं, धन्‍यवाद"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"हां"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"कैमरा"</string>
+    <string name="menu_search" msgid="7580008232297437190">"खोज"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"फ़ोटो"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"एल्बम"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"अधिक विकल्प"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"सेटिंग"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d फ़ोटो"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d फ़ोटो"</item>
+  </plurals>
 </resources>
diff --git a/res/values-hr/filtershow_strings.xml b/res/values-hr/filtershow_strings.xml
index 82e9fae..100a785 100644
--- a/res/values-hr/filtershow_strings.xml
+++ b/res/values-hr/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nije moguće učitati sliku!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Postavljanje pozadinske slike"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotografija nije preuzeta. Mreža nije dostupna."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Obrubi"</string>
-    <string name="done" msgid="3112344807927554662">"Završeno"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Poništi"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Ponovi"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Prikaži povijest"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Sakrij povijest"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Prikaži stanje slike"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Sakrij stanje slike"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Prikaži primijenjene efekte"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Sakrij primijenjene efekte"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Postavke"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Neke promjene slike nisu spremljene."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Želite li spremiti prije nego što iziđete?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Spremanje i izlaz"</string>
+    <string name="exit" msgid="242642957038770113">"Izlaz"</string>
     <string name="history" msgid="455767361472692409">"Povijest"</string>
     <string name="reset" msgid="9013181350779592937">"Poništi"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Trenutačno stanje slike"</string>
+    <string name="imageState" msgid="8632586742752891968">"Primijenjeni efekti"</string>
     <string name="compare_original" msgid="8140838959007796977">"Usporedi"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Primijeni"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Poništi"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ništa"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fiksno"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Mali planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Ekspozicija"</string>
     <string name="sharpness" msgid="6463103068318055412">"Oštrina"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Automatska boja"</string>
     <string name="hue" msgid="6231252147971086030">"Nijansa"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sjenke"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Isticanja"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Crvene oči"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Crtanje"</string>
     <string name="straighten" msgid="26025591664983528">"Poravnanje"</string>
     <string name="crop" msgid="5781263790107850771">"Obrezivanje"</string>
     <string name="rotate" msgid="2796802553793795371">"Okretanje"</string>
     <string name="mirror" msgid="5482518108154883096">"Zrcalo"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Ništa"</string>
+    <string name="edge" msgid="7036064886242147551">"Rubovi"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Smanji"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Crveno"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zeleno"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Plavo"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Veličina"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Boja"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Crte"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Oznaka"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Prskanje"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Izbriši"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Odabir prilagođene boje"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Odabir boje"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Odabir veličine"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"U redu"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Izvornik"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Rezultat"</string>
 </resources>
diff --git a/res/values-hr/photoeditor_strings.xml b/res/values-hr/photoeditor_strings.xml
deleted file mode 100644
index 7f6a174..0000000
--- a/res/values-hr/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotostudio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Nije moguće učitati fotografiju"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Nije moguće spremiti uređenu fotografiju"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Uređena fotografija spremljena u mapu <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Odbaciti nespremljene promjene?"</string>
-    <string name="yes" msgid="5402582493291792293">"Da"</string>
-    <string name="save" msgid="5516670392524294967">"SPREMI"</string>
-    <string name="autofix" msgid="1663414996270538748">"Aut. popravak"</string>
-    <string name="crop" msgid="7598378507763334041">"Obreži"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Pomak boja"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentarni"</string>
-    <string name="doodle" msgid="1686409894518940990">"Crtarija"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dvobojno"</string>
-    <string name="facelift" msgid="6205748523156172637">"Sjaj lica"</string>
-    <string name="facetan" msgid="4412831806626044111">"Preplanulo lice"</string>
-    <string name="filllight" msgid="2644989991700022526">"Ispuni svjetlom"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Riblje oko"</string>
-    <string name="flip" msgid="2357692401826287480">"Okreni"</string>
-    <string name="grain" msgid="7487585304579789098">"Zrnatost filma"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Crno-bijelo"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Crno-bijelo"</string>
-    <string name="highlight" msgid="3902653944386623972">"Isticanja"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativan"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizacija"</string>
-    <string name="redeye" msgid="4958448806369928239">"Crvene oči"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotiraj"</string>
-    <string name="saturation" msgid="8621322012271169931">"Zasićenje"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepija"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sjenčanje"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Izoštri"</string>
-    <string name="straighten" msgid="5217801513491493491">"Poravnaj"</string>
-    <string name="temperature" msgid="1607987938521534517">"Toplina"</string>
-    <string name="tint" msgid="154435943863418434">"Svijetlija nijansa"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjeta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Povucite oznake da biste obrezali sliku"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Crtanje na fotografiju za doodle logotip"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Povucite sliku da biste je preokrenuli"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Dotaknite oči da biste uklonili crvenilo"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Povucite fotografiju da biste je rotirali"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Povucite fotografiju da biste je izravnali"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efekti ekspozicije"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efekti boje"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Umjetnički efekti"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Popravi"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Poništi"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Ponovi"</string>
-</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 24747ad..b86d5ec 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rotiraj udesno"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Nije moguće pronaći stavku."</string>
     <string name="edit" msgid="1502273844748580847">"Uredi"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Osnovno uređivanje"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Obrada zahtjeva za predmemoriju"</string>
     <string name="caching_label" msgid="4521059045896269095">"U predmemoriju…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Obreži"</string>
     <string name="trim_action" msgid="703098114452883524">"Obreži"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Isključi zvuk"</string>
     <string name="set_as" msgid="3636764710790507868">"Postavi kao"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Nije moguće isključiti zvuk."</string>
     <string name="video_err" msgid="7003051631792271009">"Nije moguće reproducirati videozapis."</string>
     <string name="group_by_location" msgid="316641628989023253">"Prema lokaciji"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Po vremenu"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automatski"</string>
     <string name="flash_on" msgid="7891556231891837284">"Bljes. okinuta"</string>
     <string name="flash_off" msgid="1445443413822680010">"Bez bljesk."</string>
+    <string name="unknown" msgid="3506693015896912952">"Nepoznato"</string>
     <string name="ffx_original" msgid="372686331501281474">"Izvornik"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nema dostupne vanjske pohrane"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Prikaz filmske vrpce"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Prikaži kao rešetku"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Na cijelom zaslonu"</string>
     <string name="trimming" msgid="9122385768369143997">"Skraćivanje"</string>
+    <string name="muting" msgid="5094925919589915324">"Isključivanje zvuka"</string>
     <string name="please_wait" msgid="7296066089146487366">"Pričekajte"</string>
-    <string name="save_into" msgid="4960537214388766062">"Spremanje obrezanog videozapisa u album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Spremanje videozapisa u album <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nije moguće skratiti: ciljani videozapis prekratak je"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Obrada prikaza panorame"</string>
     <string name="save" msgid="613976532235060516">"Spremi"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skeniranje sadržaja..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d skeniranih stavki"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d skenirana stavka"</item>
+    <item quantity="other" msgid="3138021473860555499">"Broj skeniranih stavki: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Razvrstavanje…"</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skeniranje završeno"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Uvoz..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nema dostupnog sadržaja za uvoz na taj uređaj."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nije povezan nijedan MTP uređaj"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Pogreška fotoaparata"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Povezivanje s fotoaparatom nije moguće."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Fotoaparat je onemogućen zbog sigurnosnih pravila."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotoaparat"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Kamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Pričekajte…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Prije upotrebe fotoaparata instalirajte USB memoriju."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Prije upotrebe fotoaparata umetnite SD karticu."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Pripremanje USB memorije..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Priprema SD kartice…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nije moguć pristup USB pohrani."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nije moguć pristup SD kartici."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ODUSTANI"</string>
+    <string name="review_ok" msgid="1156261588693116433">"GOTOVO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Snimanje s vremenskim odmakom"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Odaberite kameru"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Natrag"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Naprijed"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Lokacija pohranjivanja"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKACIJA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Timer za odbrojavanje"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekunda"</item>
+    <item quantity="other" msgid="6455381617076792481">"Za ovoliko sekundi: %d"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Zvuk pri odbrojavanju"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Isključeno"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Uključeno"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videokvaliteta"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Visoka"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Niska"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Protek vremena"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Postavke fotoaparata"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Postavke kamere"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Veličina slike"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M piks. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapiksela"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapiksel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Način fokusa"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatski"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Beskonačno"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATSKI"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"BESKONAČNO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Način bljeskalice"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"NAČIN BLJESKALICE"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatski"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Uključeno"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Isključeno"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATSKA BLJESKALICA"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLJESKALICA UKLJUČENA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLJESKALICA ISKLJUČENA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balans bijele boje"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANS BIJELE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatski"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Svjetlosni izvor sa žarnom niti"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dnevno svjetlo"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescentno"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Oblačno"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATSKI"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"SVJETLOST ŽARULJE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DNEVNO SVJETLO"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENTNO"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"OBLAČNO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Način scene"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatski"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Radnja"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noć"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Zalazak sunca"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Zabava"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NIŠTA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"RADNJA"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOĆ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ZALAZAK SUNCA"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ZABAVA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"MJERAČ VREMENA ZA ODBROJAVANJE"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ODBROJAVANJE JE ISKLJUČENO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDA"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDE"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDI"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDI"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"U načinu scene ne može se odabirati."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Ekspozicija"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EKSPOZICIJA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"VDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PREDNJI FOTOAPARAT"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"STRAŽNJI FOTOAPARAT"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"U redu"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Na vašoj USB memoriji ponestaje prostora. Primijenite postavku kvalitete ili izbrišite neke slike ili druge datoteke."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Na vašoj kartici SD ponestaje prostora. Primijenite postavku kvalitete ili izbrišite neke slike ili druge datoteke."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Dostignuto je ograničenje veličine."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Prebrzo"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Priprema panorame"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panoramu nije moguće spremiti."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Snimanje panorame"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Čekanje na prethodnu panoramu"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Spremanje..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Obrada prikaza panorame"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Dodirnite za fokus."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efekti"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ništa"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Stisnuto"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Velike oči"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Velika usta"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Mala usta"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Veliki nos"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Male oči"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"U svemiru"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Zalazak sunca"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Vaš videozapis"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"U nastavku postavite ​​uređaj."\n"Odmaknite se na trenutak."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Dodirnite za fotografiranje tijekom snimanja."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Pokrenuto je snimanje videozapisa."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Zaustavljeno je snimanje videozapisa."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Fotografiranje је onemogućeno kada su posebni efekti uključeni."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Obriši efekte"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ŠAŠAVA LICA"</string>
+    <string name="effect_background" msgid="6579360207378171022">"POZADINA"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Gumb Okidač"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Tipka izbornika"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Najnovija fotografija"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Prednji i stražnji prekidač fotoaparata"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Birač fotoaparata, videozapisa ili panorame"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Više kontrola postavke"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Kontrole postavke zatvaranja"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Upravljanje povećavanjem/smanjivanjem"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Smanjenje %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Povećanje %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Potvrdni okvir %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Prebaci na fotografiju"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Prebaci na videozapis"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Prebaci na panoramski način"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Prijeđi na novu panoramu"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Odustani"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Završeno"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Ponovi snimanje u pregledu"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Reproduciraj videozapis"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pauziraj videozapis"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Ponovno učitaj videozapis"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Vremenska traka reprodukcije videozapisa"</string>
+    <string name="capital_on" msgid="5491353494964003567">"UKLJUČI"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ISKLJUČI"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Isključeno"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 sat"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 sata"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 sata"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 sata"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 sati"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 sata"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunde"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minute"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"sati"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Gotovo"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Postavljanje vrem. intervala"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Značajka protoka vremena isključena je. Uključite ju da biste postavili vremenski interval."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Odbrojavanje je isključeno. Uključite odbrojavanje prije snimanja fotografije."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Postavite trajanje u sekundama"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Odbrojavanje do snimanja fotografije"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Zapamtiti lokacije fotografija?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Dodajte svojim fotografijama i videozapisima oznake lokacija na kojima su snimljeni."\n\n"Ostale aplikacije mogu pristupiti tim podacima s vašim spremljenim slikama."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ne, hvala"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotoaparat"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Pretraživanje"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotografije"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"DODATNE OPCIJE"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"POSTAVKE"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d slika"</item>
+    <item quantity="other" msgid="3813306834113858135">"Br. slika: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-hu/filtershow_strings.xml b/res/values-hu/filtershow_strings.xml
index f699dc3..1674b2c 100644
--- a/res/values-hu/filtershow_strings.xml
+++ b/res/values-hu/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nem sikerült betölteni a képet!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Háttérkép beállítása"</string>
+    <string name="download_failure" msgid="5923323939788582895">"A fénykép letöltése nem sikerült. A hálózat nem érhető el."</string>
     <string name="original" msgid="3524493791230430897">"Eredeti"</string>
     <string name="borders" msgid="2067345080568684614">"Szegélyek"</string>
-    <string name="done" msgid="3112344807927554662">"Kész"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Visszavonás"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Ismétlés"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Előzmények"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Előzmények elrejtése"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Képállapot"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Állapot elrejtés"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Alkalmazott effektek"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Alkalmazott effektek elrejtése"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Beállítások"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Nem mentett módosítások vannak a képen."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Szeretne menteni a kilépés előtt?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Mentés és kilépés"</string>
+    <string name="exit" msgid="242642957038770113">"Kilépés"</string>
     <string name="history" msgid="455767361472692409">"Előzmények"</string>
     <string name="reset" msgid="9013181350779592937">"Visszaállítás"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Aktuális fényképállapot"</string>
+    <string name="imageState" msgid="8632586742752891968">"Alkalmazott hatások"</string>
     <string name="compare_original" msgid="8140838959007796977">"Összehasonlítás"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Alkalmaz"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Visszaállítás"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nincs"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Rögzített"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Kisbolygó"</string>
     <string name="exposure" msgid="6526397045949374905">"Expozíció"</string>
     <string name="sharpness" msgid="6463103068318055412">"Élesség"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Automat. szín"</string>
     <string name="hue" msgid="6231252147971086030">"Színárnyalat"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Árnyékok"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Kiemelések"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Görbék"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetta"</string>
     <string name="redeye" msgid="4508883127049472069">"Vörösszem"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Rajzok"</string>
     <string name="straighten" msgid="26025591664983528">"Kiegyenesítés"</string>
     <string name="crop" msgid="5781263790107850771">"Körülvágás"</string>
     <string name="rotate" msgid="2796802553793795371">"Forgatás"</string>
     <string name="mirror" msgid="5482518108154883096">"Tükrözés"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatív"</string>
     <string name="none" msgid="6633966646410296520">"Nincs"</string>
+    <string name="edge" msgid="7036064886242147551">"Szélek"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Kicsinyítés"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Piros"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zöld"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Kék"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stílus"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Méret"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Szín"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Vonalak"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Filctoll"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Fröcskölés"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Törlés"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Egyéni szín kiválasztása"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Szín kiválasztása"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Méret kiválasztása"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Eredeti"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Eredmény"</string>
 </resources>
diff --git a/res/values-hu/photoeditor_strings.xml b/res/values-hu/photoeditor_strings.xml
deleted file mode 100644
index 3607c64..0000000
--- a/res/values-hu/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Nem sikerült betölteni a fotót"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"A szerkesztett fotót nem tudtuk menteni"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"A Szerkesztett fotót <xliff:g id="FOLDER_NAME">%s</xliff:g> mappába mentettük"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Elveti a nem mentett módosításokat?"</string>
-    <string name="yes" msgid="5402582493291792293">"Igen"</string>
-    <string name="save" msgid="5516670392524294967">"MENTÉS"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autom. javítás"</string>
-    <string name="crop" msgid="7598378507763334041">"Körbevágás"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Áttűnés"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentumfilm"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Kéttónusú"</string>
-    <string name="facelift" msgid="6205748523156172637">"Arcfény"</string>
-    <string name="facetan" msgid="4412831806626044111">"Arcszín"</string>
-    <string name="filllight" msgid="2644989991700022526">"Háttérfény"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Halszem"</string>
-    <string name="flip" msgid="2357692401826287480">"Tükrözés"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmszemcse"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Fekete-fehér"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Fekete-fehér"</string>
-    <string name="highlight" msgid="3902653944386623972">"Kiemelt részek"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatív"</string>
-    <string name="posterize" msgid="4139212359561383385">"Poszterizálás"</string>
-    <string name="redeye" msgid="4958448806369928239">"Vörösszem"</string>
-    <string name="rotate" msgid="6607597269792373083">"Forgatás"</string>
-    <string name="saturation" msgid="8621322012271169931">"Telítettség"</string>
-    <string name="sepia" msgid="7978093531824705601">"Szépia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Árnyékok"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Élesítés"</string>
-    <string name="straighten" msgid="5217801513491493491">"Kiegyenesítés"</string>
-    <string name="temperature" msgid="1607987938521534517">"Színhőmérsékl."</string>
-    <string name="tint" msgid="154435943863418434">"Árnyalás"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignetta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Húzza a jelölőket a fotó vágásához"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Rajzoljon a fotóra embléma létrehozásához"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Húzza a fotót annak tükrözéséhez"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Vörös szemek eltávolítása megérintéssel"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Húzza a fényképet annak forgatásához"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Húzza a fényképet annak kiegyenesítéséhez"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Expozíciós hatások"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Színhatások"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Művészi hatások"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Helyreállítás"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Visszavonás"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Ismétlés"</string>
-</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 481cb1f..830ab1f 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Forgatás jobbra"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Nem található az elem."</string>
     <string name="edit" msgid="1502273844748580847">"Szerkesztés"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Egyszerű szerkesztés"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Tárolási kérelmek feldolgozása"</string>
     <string name="caching_label" msgid="4521059045896269095">"Gyorsítótárazás..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Körbevágás"</string>
     <string name="trim_action" msgid="703098114452883524">"Vágás"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Némítás"</string>
     <string name="set_as" msgid="3636764710790507868">"Beállítás, mint"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"A videó nem némítható."</string>
     <string name="video_err" msgid="7003051631792271009">"Nem lehet lejátszani a videót."</string>
     <string name="group_by_location" msgid="316641628989023253">"Helyszín szerint"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Idő szerint"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automata"</string>
     <string name="flash_on" msgid="7891556231891837284">"Vakuvillanás"</string>
     <string name="flash_off" msgid="1445443413822680010">"Vaku nélkül"</string>
+    <string name="unknown" msgid="3506693015896912952">"Ismeretlen"</string>
     <string name="ffx_original" msgid="372686331501281474">"Eredeti"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Szépia"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Azonnali"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nincs elérhető külső tárhely"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmszalag nézet"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Rácsnézet"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Teljes képernyő"</string>
     <string name="trimming" msgid="9122385768369143997">"Vágás"</string>
+    <string name="muting" msgid="5094925919589915324">"Némítás..."</string>
     <string name="please_wait" msgid="7296066089146487366">"Kérjük, várjon."</string>
-    <string name="save_into" msgid="4960537214388766062">"Megvágott videó mentése az albumba:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Videó mentése ide: <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nem lehet megvágni: a célvideó túl rövid."</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panorámakép megjelenítése"</string>
     <string name="save" msgid="613976532235060516">"Mentés"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Tartalom beolvasása..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elem beolvasva"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d elem beolvasva"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elem beolvasva"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Rendezés..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"A beolvasás befejeződött."</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importálás..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nem áll rendelkezésre az eszközre importálható tartalom."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nincs MTP-eszköz csatlakoztatva"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerahiba"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nem lehet csatlakozni a kamerához."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"A biztonsági házirendek miatt a kamera le van tiltva."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Kérjük, várjon..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Kérjük, csatoljon egy USB-tárat a kamera használata előtt."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"A kamera használatához helyezzen be egy SD-kártyát."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Az USB-tár előkészítése..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD-kártya előkészítése..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nem lehet hozzáférni az USB-háttértárhoz"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nem lehet hozzáférni az SD-kártyához"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"MÉGSE"</string>
+    <string name="review_ok" msgid="1156261588693116433">"KÉSZ"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Gyorsított felvétel"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Válasszon kamerát"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Vissza"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Előre néző"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Hely tárolása"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"HELY"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Visszaszámlálás-időzítő"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 másodperc"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d másodperc"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Hangjelzéssel"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Ki"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Be"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videó minősége"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Magas"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Alacsony"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Gyorsított felvétel"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kamera beállításai"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokamera beállításai"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Képméret"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 MP (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fókuszálás módja"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatikus"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Végtelen"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makró"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATIKUS"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"VÉGTELEN"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRÓ"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Vakumód"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"VAKU MÓD"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatikus"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Be"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Ki"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATIKUS VAKU"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"VAKU BEKAPCSOLVA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"VAKU KIKAPCSOLVA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Fehéregyensúly"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"FEHÉREGYENSÚLY"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatikus"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Izzólámpa"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Nappali fény"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fénycső"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Felhős"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATIKUS"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"IZZÓLÁMPA"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"NAPPALI FÉNY"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FÉNYCSŐ"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"FELHŐS"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Kép jellege"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatikus"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Akció"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Éjszakai"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Napnyugta"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Buli"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"EGYIK SEM"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AKCIÓ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ÉJSZAKA"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"NAPNYUGTA"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"BULI"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"VISSZASZÁMLÁLÁS-IDŐZÍTŐ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"IDŐZÍTŐ KI"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 MÁSODPERC"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 MÁSODPERC"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 MÁSODPERC"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 MÁSODPERC"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Nem választható ebben a módban."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Megvilágítás"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPONÁLÁS"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ELÜLSŐ KAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"HÁTSÓ KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Az USB-táron kezd elfogyni a hely. Módosítsa a minőségi beállításokat, vagy töröljön néhány képet vagy egyéb fájlt."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Az SD-kártyán kezd elfogyni a hely. Módosítsa a minőségi beállításokat, vagy töröljön néhány képet vagy egyéb fájlt."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"A videó elérte a méretkorlátot."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Túl gyors"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panoráma előkészítése"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Nem lehet menteni a panorámát."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoráma"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panoráma rögzítése"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Várakozás az előző panorámára"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Mentés..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panorámakép megjelenítése"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"A fókuszáláshoz érintse meg."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Hatások"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Nincs"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Összenyomás"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Nagy szemek"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Nagy száj"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Kis száj"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nagy orr"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Kis szemek"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Az űrben"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Napnyugta"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Saját videó"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Tegye le a készüléket."\n"Lépjen ki a képből egy pillanatra."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Érintse meg fotó készítéséhez felvétel közben."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"A videorögzítés elindult."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"A videorögzítés leállt."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"A video-pillanatfelvétel letiltva speciális effektek esetén."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Effektek törlése"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"BOLONDOS ARCOK"</string>
+    <string name="effect_background" msgid="6579360207378171022">"HÁTTÉR"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Exponáló gomb"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menü gomb"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Legutóbbi fotó"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Első és hátsó kamera kapcsolója"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kamera, videó vagy panoráma kiválasztása"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"További beállításvezérlők"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Beállításvezérlők bezárása"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Nagyítás/kicsinyítés vezérlése"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Csökkentés: %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Növelés: %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s jelölőnégyzet"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Fényképező mód"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Videó mód"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Panoráma mód"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Váltás az új Panoráma módra"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Törlés"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Kész"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Ellenőrzés – új felvétel"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Videó lejátszása"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Videó szüneteltetése"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Videó újratöltése"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Videolejátszó idősávja"</string>
+    <string name="capital_on" msgid="5491353494964003567">"BE"</string>
+    <string name="capital_off" msgid="7231052688467970897">"KI"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Ki"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 másodperc"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 perc"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 óra"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 óra"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"másodperc"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"perc"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"óra"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Kész"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Időintervallum beállítása"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"A Gyorsított felvétel funkció ki van kapcsolva. Kapcsolja be az időintervallum beállításához."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"A visszaszámlálás-időzítő ki van kapcsolva. A fénykép készítése előtti visszaszámláláshoz kapcsolja be."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Időtartam megadása másodpercben"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Visszaszámlálás a fényképezésig"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Szeretné eltárolni a felvételek helyszínét?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Címkézze fel a fotókat és videókat a hellyel, ahol készítette őket."\n\n"Más alkalmazások hozzáférnek ehhez az információhoz és a mentett képekhez."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nem, köszönöm."</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Igen"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Keresés"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotók"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumok"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"TOVÁBBI LEHETŐSÉGEK"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"BEÁLLÍTÁSOK"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotó"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotó"</item>
+  </plurals>
 </resources>
diff --git a/res/values-in/filtershow_strings.xml b/res/values-in/filtershow_strings.xml
index d5d9e42..6e2ae2d 100644
--- a/res/values-in/filtershow_strings.xml
+++ b/res/values-in/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Tidak dapat memuat gambar!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Menyetel wallpaper"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Tidak dapat mengunduh foto. Jaringan tidak tersedia."</string>
     <string name="original" msgid="3524493791230430897">"Asli"</string>
     <string name="borders" msgid="2067345080568684614">"Batas"</string>
-    <string name="done" msgid="3112344807927554662">"Selesai"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Batalkan"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Ulangi"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Tampilkan Riwayat"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Sembunyikan Riwayat"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Tampilkan Status"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Sembunyikan Status"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Tampilkan Efek yang Diterapkan"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Sembunyikan Efek yang Diterapkan"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Setelan"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Ada perubahan yang belum tersimpan pada gambar ini."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Ingin menyimpan sebelum keluar?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Simpan dan Keluar"</string>
+    <string name="exit" msgid="242642957038770113">"Keluar"</string>
     <string name="history" msgid="455767361472692409">"Riwayat"</string>
     <string name="reset" msgid="9013181350779592937">"Setel Ulang"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Status Gambar Saat Ini"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efek yang Diterapkan"</string>
     <string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Terapkan"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Setel Ulang"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Tidak ada"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Tetap"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planet Mini"</string>
     <string name="exposure" msgid="6526397045949374905">"Pencahayaan"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ketajaman"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Warna Otomatis"</string>
     <string name="hue" msgid="6231252147971086030">"Rona"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bayangan"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Sorotan"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurva"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyet"</string>
     <string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Gambar"</string>
     <string name="straighten" msgid="26025591664983528">"Luruskan"</string>
     <string name="crop" msgid="5781263790107850771">"Pangkas"</string>
     <string name="rotate" msgid="2796802553793795371">"Putar"</string>
     <string name="mirror" msgid="5482518108154883096">"Cermin"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatif"</string>
     <string name="none" msgid="6633966646410296520">"Tidak ada"</string>
+    <string name="edge" msgid="7036064886242147551">"Tepi"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Downsample"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Merah"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Hijau"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Biru"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Gaya"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Ukuran"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Warna"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Garis"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Penanda"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Percikan"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Hapus"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Pilih warna khusus"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Pilih Warna"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Pilih Ukuran"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Oke"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Asli"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Hasil"</string>
 </resources>
diff --git a/res/values-in/photoeditor_strings.xml b/res/values-in/photoeditor_strings.xml
deleted file mode 100644
index c88d21c..0000000
--- a/res/values-in/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Tidak dapat memuat foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Tidak dapat menyimpan foto yang diedit"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Foto yang diedit disimpan di <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Buang perubahan yang tidak tersimpan?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ya"</string>
-    <string name="save" msgid="5516670392524294967">"SIMPAN"</string>
-    <string name="autofix" msgid="1663414996270538748">"Prbaikan Otomts"</string>
-    <string name="crop" msgid="7598378507763334041">"Pangkas"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proses silang"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumenter"</string>
-    <string name="doodle" msgid="1686409894518940990">"Orat-oret"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dwiwarna"</string>
-    <string name="facelift" msgid="6205748523156172637">"Wajah Berkilau"</string>
-    <string name="facetan" msgid="4412831806626044111">"Wajh Sawo Matng"</string>
-    <string name="filllight" msgid="2644989991700022526">"Beri Cahaya"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Mata ikan"</string>
-    <string name="flip" msgid="2357692401826287480">"Balik"</string>
-    <string name="grain" msgid="7487585304579789098">"Butiran Film"</string>
-    <string name="grayscale" msgid="7641563843060945228">"H&amp;P"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Hitam-putih"</string>
-    <string name="highlight" msgid="3902653944386623972">"Sorotan"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterisasi"</string>
-    <string name="redeye" msgid="4958448806369928239">"Mata Merah"</string>
-    <string name="rotate" msgid="6607597269792373083">"Putar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturasi"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Bayangan"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Pertajam"</string>
-    <string name="straighten" msgid="5217801513491493491">"Luruskan"</string>
-    <string name="temperature" msgid="1607987938521534517">"Kehangatan"</string>
-    <string name="tint" msgid="154435943863418434">"Warna"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinyet"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Seret penanda untuk memangkas foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Menggambar pada foto untuk membuat orat-oret"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Seret foto untuk membaliknya"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Ketuk mata merah untuk menghapusnya"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Seret foto untuk memutarnya"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Seret foto untuk meluruskannya"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efek pencahayaan"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efek warna"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efek artistik"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Perbaiki"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Urungkan"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Ulangi"</string>
-</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 8219464..e7b86df 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Putar ke kanan"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Tidak dapat menemukan item."</string>
     <string name="edit" msgid="1502273844748580847">"Edit"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edit Sederhana"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Memproses permintaan menyimpan ke cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Smpn ke tmbolok"</string>
     <string name="crop_action" msgid="3427470284074377001">"Pangkas"</string>
     <string name="trim_action" msgid="703098114452883524">"Pangkas"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Bisukan"</string>
     <string name="set_as" msgid="3636764710790507868">"Setel sebagai"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Tidak dapat membisukan video."</string>
     <string name="video_err" msgid="7003051631792271009">"Tidak dapat memutar video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Menurut lokasi"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Menurut waktu"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Otomatis"</string>
     <string name="flash_on" msgid="7891556231891837284">"Lampu kilat aktif"</string>
     <string name="flash_off" msgid="1445443413822680010">"Tanpa lampu kilat"</string>
+    <string name="unknown" msgid="3506693015896912952">"Tidak diketahui"</string>
     <string name="ffx_original" msgid="372686331501281474">"Asli"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Lawas"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instan"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Tidak ada penyimpanan eksternal yang tersedia"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Tampilan strip film"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Tampilan kisi"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Tampilan layar penuh"</string>
     <string name="trimming" msgid="9122385768369143997">"Pemangkasan"</string>
+    <string name="muting" msgid="5094925919589915324">"Membisukan"</string>
     <string name="please_wait" msgid="7296066089146487366">"Harap tunggu"</string>
-    <string name="save_into" msgid="4960537214388766062">"Menyimpan video yang dipangkas ke dalam album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Menyimpan video ke <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Tidak dapat memangkas : video target terlalu pendek"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Merender panorama"</string>
     <string name="save" msgid="613976532235060516">"Simpan"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Memindai konten..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d item dipindai"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item dipindai"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d item dipindai"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Menyortir…"</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Pemindaian selesai"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Mengimpor..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Tidak ada konten yang dapat diimpor pada perangkat ini."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Tidak ada perangkat MTP yang tersambung"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kesalahan kamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Tidak dapat terhubung ke kamera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera telah dinonaktifkan karena kebijakan keamanan."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Perekam video"</string>
+    <string name="wait" msgid="8600187532323801552">"Harap tunggu…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Pasang penyimpanan USB sebelum menggunakan kamera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Masukkan kartu SD sebelum menggunakan kamera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Menyiapkan penyimpanan USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Menyiapkan kartu SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Tidak dapat mengakses penyimpanan USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Tidak dapat mengakses kartu SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"BATAL"</string>
+    <string name="review_ok" msgid="1156261588693116433">"SELESAI"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Perekaman time lapse"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Pilih kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Belakang"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Depan"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Lokasi penyimpanan"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKASI"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Penghitung mundur"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 detik"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d detik"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Bip penghitungan"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Nonaktif"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Aktif"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kualitas video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Tinggi"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Rendah"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Selang waktu"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Setelan kamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Setelan perekam video"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Ukuran gambar"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M piksel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M piksel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M piksel"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M piksel (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Mode fokus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Otomatis"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Ketakterbatasan"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"OTOMATIS"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"TAK TERBATAS"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Mode lampu kilat"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODE LAMPU KILAT"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Otomatis"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Pada"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Mati"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"LAMPU KILAT OTOMATIS"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"LAMPU KILAT NYALA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"LAMPU KILAT MATI"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Keseimbangan putih"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"KESEIMBANGAN PUTIH"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Otomatis"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Berpijar"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Siang Hari"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluoresens"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Berawan"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"OTOMATIS"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"BERPIJAR"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"SIANG HARI"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESENS"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"BERAWAN"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Mode adegan"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Otomatis"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Aksi"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Malam"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Matahari terbenam"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Pesta"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"TIDAK ADA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"TINDAKAN"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"MALAM"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"MATAHARI TERBENAM"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"PENGHITUNG WAKTU MUNDUR"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"PENGHITUNG WAKTU NONAKTIF"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 DETIK"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 DETIK"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 DETIK"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 DETIK"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Tidak dapat dipilih dalam mode adegan."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Pencahayaan"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"PENCAHAYAAN"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"KAMERA DEPAN"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"KAMERA BELAKANG"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Oke"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Penyimpanan USB Anda kehabisan ruang kosong. Ubah setelan kualitas atau hapus beberapa gambar atau file lain."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Kartu SD Anda kehabisan ruang kosong. Ubah setelan kualitas atau hapus beberapa gambar atau file lain."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Batas ukuran tercapai."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Terlalu cpt"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Menyiapkan panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Tidak dapat menyimpan panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Mengambil gambar panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Menunggu panorama sebelumnya"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Menyimpan…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Merender panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Sentuh untuk memfokuskan."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efek"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Tidak Ada"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Peras"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Mata besar"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Mulut besar"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Mulut kecil"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Hidung besar"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Mata kecil"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Luar angkasa"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Mthari trbenam"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Video Anda"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Letakkan perangkat Anda."\n"Menyingkirlah sejenak dari bidang bidik."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Sentuh untuk mengambil foto saat sedang merekam."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Perekaman video telah dimulai."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Perekaman video telah berhenti."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Cuplikan video dinonaktifkan bila efek khusus aktif."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Hapus efek"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"WAJAH KONYOL"</string>
+    <string name="effect_background" msgid="6579360207378171022">"LATAR BELAKANG"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Tombol rana"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Tombol menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto terbaru"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Beralih kamera depan dan belakang"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Pemilih kamera, video, atau panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Kontrol setelan lainnya"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Tutup kontrol setelan"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Kontrol perbesar/perkecil"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Kurangi %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Tingkatkan %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s kotak centang"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Alihkan ke foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Alihkan ke video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Alihkan ke panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Beralih ke panorama baru"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Tinjauan dibatalkan"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Tinjauan selesai"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Tinjau pengambilan ulang"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Putar video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Jeda video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Muat ulang video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Bilah waktu pemutar video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"NYALA"</string>
+    <string name="capital_off" msgid="7231052688467970897">"MATI"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Nonaktif"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 detik"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 menit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 jam"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"detik"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"menit"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"jam"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Selesai"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Setel Interval Waktu"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Fitur selang waktu tidak aktif. Aktifkan untuk menyetel interval waktu."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Penghitung mundur tidak aktif. Aktifkan untuk menghitung mundur sebelum mengambil foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Setel durasi dalam detik"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Menghitung mundur untuk mengambil foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Ingat lokasi foto?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Beri tag foto dan video Anda dengan lokasi tempat pengambilannya."\n\n"Aplikasi lain dapat mengakses informasi ini beserta gambar Anda yang tersimpan."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Tidak, terima kasih"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ya"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Telusuri"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"OPSI LAINNYA"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"SETELAN"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foto"</item>
+  </plurals>
 </resources>
diff --git a/res/values-it/filtershow_strings.xml b/res/values-it/filtershow_strings.xml
index 33f6012..1336819 100644
--- a/res/values-it/filtershow_strings.xml
+++ b/res/values-it/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Impossibile caricare l\'immagine."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Impostazione dello sfondo"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Impossibile scaricare la foto. Rete non disponibile."</string>
     <string name="original" msgid="3524493791230430897">"Originale"</string>
     <string name="borders" msgid="2067345080568684614">"Bordi"</string>
-    <string name="done" msgid="3112344807927554662">"Fine"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Annulla"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Ripeti"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostra cronologia"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Nascondi cronologia"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Mostra stato immagine"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Nascondi stato img"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostra effetti applicati"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Nascondi effetti applicati"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Impostazioni"</string>
+    <string name="unsaved" msgid="8704442449002374375">"L\'immagine ha modifiche non salvate."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vuoi salvare prima di uscire?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Salva ed esci"</string>
+    <string name="exit" msgid="242642957038770113">"Esci"</string>
     <string name="history" msgid="455767361472692409">"Cronologia"</string>
     <string name="reset" msgid="9013181350779592937">"Reimposta"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Stato immagine attuale"</string>
+    <string name="imageState" msgid="8632586742752891968">"Effetti applicati"</string>
     <string name="compare_original" msgid="8140838959007796977">"Confronta"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Applica"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Reimposta"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nessuno"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fisse"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Pianetino"</string>
     <string name="exposure" msgid="6526397045949374905">"Esposizione"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidezza"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Colore autom."</string>
     <string name="hue" msgid="6231252147971086030">"Tonalità"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ombre"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Alte luci"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curve"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetta"</string>
     <string name="redeye" msgid="4508883127049472069">"Occhi rossi"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Disegna"</string>
     <string name="straighten" msgid="26025591664983528">"Raddrizza"</string>
     <string name="crop" msgid="5781263790107850771">"Ritaglia"</string>
     <string name="rotate" msgid="2796802553793795371">"Ruota"</string>
     <string name="mirror" msgid="5482518108154883096">"Specchio"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativo"</string>
     <string name="none" msgid="6633966646410296520">"Nessuno"</string>
+    <string name="edge" msgid="7036064886242147551">"Bordi"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Sottocampionare"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rosso"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blu"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stile"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Dimensioni"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Colore"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linee"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Evidenziatore"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spruzzo"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Cancella"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Scegli colore personalizzato"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Seleziona colore"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Seleziona dimensioni"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Originale"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Risultato"</string>
 </resources>
diff --git a/res/values-it/photoeditor_strings.xml b/res/values-it/photoeditor_strings.xml
deleted file mode 100644
index d35bded..0000000
--- a/res/values-it/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Impossibile caricare la foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Impossibile salvare la foto modificata"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Foto modificata salvata in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Ignorare le modifiche non salvate?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sì"</string>
-    <string name="save" msgid="5516670392524294967">"SALVA"</string>
-    <string name="autofix" msgid="1663414996270538748">"Correzione autom."</string>
-    <string name="crop" msgid="7598378507763334041">"Ritaglia"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Cross process"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentario"</string>
-    <string name="doodle" msgid="1686409894518940990">"Scarabocchio"</string>
-    <string name="duotone" msgid="8145893940788467106">"Bitonale"</string>
-    <string name="facelift" msgid="6205748523156172637">"Luminosità viso"</string>
-    <string name="facetan" msgid="4412831806626044111">"Colorito viso"</string>
-    <string name="filllight" msgid="2644989991700022526">"Luce riempimento"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Capovolgi"</string>
-    <string name="grain" msgid="7487585304579789098">"Grana pellicola"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Bianco e nero"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Bianco e nero"</string>
-    <string name="highlight" msgid="3902653944386623972">"Alte luci"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Effetto Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizza"</string>
-    <string name="redeye" msgid="4958448806369928239">"Occhi rossi"</string>
-    <string name="rotate" msgid="6607597269792373083">"Ruota"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturazione"</string>
-    <string name="sepia" msgid="7978093531824705601">"Seppia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Ombre"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Nitidezza"</string>
-    <string name="straighten" msgid="5217801513491493491">"Raddrizza"</string>
-    <string name="temperature" msgid="1607987938521534517">"Calore"</string>
-    <string name="tint" msgid="154435943863418434">"Tinta"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignetta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Trascina gli indicatori per ritagliare la foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Disegna sulla foto per scarabocchiarla"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Trascina la foto per girarla"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Tocca gli occhi rossi per rimuoverli"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Trascina la foto per ruotarla"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Trascina la foto per raddrizzarla"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Effetti di esposizione"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Effetti di colore"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Effetti artistici"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Correggi"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Annulla"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Ripeti"</string>
-</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 64e7fa1..5ca07b9 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Ruota a destra"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Impossibile trovare l\'elemento."</string>
     <string name="edit" msgid="1502273844748580847">"Modifica"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Modifica semplice"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Elaborazione richieste memorizzazione cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Memorizzazione..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Ritaglia"</string>
     <string name="trim_action" msgid="703098114452883524">"Ritaglia"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Disattiva audio"</string>
     <string name="set_as" msgid="3636764710790507868">"Imposta come"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Imposs. disattiv. audio video."</string>
     <string name="video_err" msgid="7003051631792271009">"Impossibile riprodurre il video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Per luogo"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Per data"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Autom."</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash scattato"</string>
     <string name="flash_off" msgid="1445443413822680010">"Senza flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Sconosciuta"</string>
     <string name="ffx_original" msgid="372686331501281474">"Originale"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nessun archivio esterno disponibile"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Visualizzazione sequenza"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Visualizzazione griglia"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Schermo intero"</string>
     <string name="trimming" msgid="9122385768369143997">"Taglio in corso"</string>
+    <string name="muting" msgid="5094925919589915324">"Disattivazione audio"</string>
     <string name="please_wait" msgid="7296066089146487366">"Attendi"</string>
-    <string name="save_into" msgid="4960537214388766062">"Salvataggio del video ritagliato nell\'album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Salvataggio del video in <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Impossibile tagliare: video di destinazione troppo breve"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Creazione panorama in corso"</string>
     <string name="save" msgid="613976532235060516">"Salva"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Scansione dei contenuti..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elementi sottoposti a scansione"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d elemento sottoposto a scansione"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elementi sottoposti a scansione"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Ordinamento..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scansione eseguita"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importazione..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Sul dispositivo non sono presenti contenuti da importare."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nessun dispositivo MTP collegato"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Errore fotocamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Impossibile collegarsi alla fotocamera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"La fotocamera è stata disattivata in base a norme di sicurezza."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotocamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videocamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Attendere..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Monta l\'archivio USB prima di utilizzare la fotocamera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Per usare la fotocamera devi inserire una scheda SD."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparazione archivio USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparazione scheda SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Accesso ad archivio USB non riuscito."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Accesso a scheda SD non riuscito."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ANNULLA"</string>
+    <string name="review_ok" msgid="1156261588693116433">"FINE"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Registrazione al rallentatore"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Scegli fotocamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Posteriore"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Frontale"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Registra località"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCALITÀ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Timer conto alla rovescia"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 secondo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d secondi"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Bip conto alla rov."</string>
+    <string name="setting_off" msgid="4480039384202951946">"Disattivata"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Attiva"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualità video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Bassa"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Rallentatore"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Impostazioni fotocamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Impostazioni videocamera"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Dimensioni foto"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 MP"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 Mpixel"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Mpixel (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 MP"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 MP"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Messa a fuoco"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatica"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinito"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Modalità flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODALITÀ FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatica"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Attiva"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Non attiva"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ATTIVO"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DISATTIVATO"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Bilanciamento bianco"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BILANCIAMENTO DEL BIANCO"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatico"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Luce incandescenza"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Luce diurna"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Luce neon"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nuvoloso"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"LUCE INCANDESCENZA"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUCE DIURNA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"LUCE NEON"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUVOLOSO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modalità scena"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatica"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Movimento"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Notturna"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Tramonto"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Festa"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NESSUNA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AZIONE"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOTTE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"TRAMONTO"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"TIMER CONTO ALLA ROVESCIA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TIMER DISATTIVATO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SECONDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SECONDI"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SECONDI"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SECONDI"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Non selezionabile in modalità scena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Esposizione"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ESPOSIZIONE"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FOTOCAMERA ANTERIORE"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"FOTOCAMERA POSTERIORE"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Lo spazio dell\'archivio USB si sta esaurendo. Cambia l\'impostazione di qualità o elimina alcune immagini o altri file."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Lo spazio della scheda SD si sta esaurendo. Cambia l\'impostazione di qualità o elimina alcune immagini o altri file."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Limite di dimensione raggiunto."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Troppo veloce"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Preparazione panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Salvataggio panorama non riuscito."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoramica"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Acquisizione panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"In attesa di panorama precedente"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Salvataggio..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Creazione panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Tocca per mettere a fuoco."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effetti"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Nessuno"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Schiaccia"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Occhi grandi"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Bocca grande"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Bocca piccola"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Naso grande"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Occhi piccoli"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Nello spazio"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Tramonto"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Il tuo video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Posa il tuo dispositivo."\n"Esci dalla visualizzazione per un attimo."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Tocca per scattare foto durante la registrazione."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"La registrazione video è stata avviata."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"La registrazione video è stata interrotta."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Istantanea video disabilitata se gli effetti speciali sono attivi."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Cancella effetti"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"FACCINE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"SFONDO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Pulsante di scatto"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Pulsante Menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto più recente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Interruttore fotocamera anteriore e posteriore"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Selettore fotocamera, video o panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Altri controlli"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Chiudi controlli"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Controllo zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Riduci %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Aumenta %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Casella di controllo %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Passa a foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Passa a video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Passa a panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Passa al nuovo panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Annulla verifica"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Verifica terminata"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Scatta/Riprendi di nuovo per recensione"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Riproduci video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Metti in pausa video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Ricarica video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra temporale del video player"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ON"</string>
+    <string name="capital_off" msgid="7231052688467970897">"OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Non attivo"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 secondo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 secondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ore"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"secondi"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuti"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ore"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Fine"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Imposta l\'intervallo di tempo"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"La funzione Rallentatore non è attiva. Attivala per impostare l\'intervallo di tempo."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Timer del conto alla rovescia non attivo. Attivalo per il conto alla rovescia prima di scattare una foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Imposta la durata in secondi"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Conto alla rovescia per scattare una foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Memorizzare i luoghi delle foto?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Aggiungi alle foto e ai video tag relativi alle località in cui sono stati ripresi."\n\n"Altre applicazioni possono accedere a queste informazioni e alle tue immagini salvate."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"No, grazie"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sì"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotocamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Cerca"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ALTRE OPZIONI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"IMPOSTAZIONI"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foto"</item>
+  </plurals>
 </resources>
diff --git a/res/values-iw/filtershow_strings.xml b/res/values-iw/filtershow_strings.xml
index 95942dc..b584872 100644
--- a/res/values-iw/filtershow_strings.xml
+++ b/res/values-iw/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"לא ניתן להעלות את התמונה!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"מגדיר טפט"</string>
+    <string name="download_failure" msgid="5923323939788582895">"לא ניתן היה להוריד את התמונה. הרשת לא זמינה."</string>
     <string name="original" msgid="3524493791230430897">"מקור"</string>
     <string name="borders" msgid="2067345080568684614">"גבולות"</string>
-    <string name="done" msgid="3112344807927554662">"בוצע"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"בטל"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"בצע מחדש"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"הצג היסטוריה"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"הסתר היסטוריה"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"הצג מצב תמונה"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"הסתר מצב תמונה"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"הצג אפקטים שהוחלו"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"הסתר אפקטים שהוחלו"</string>
     <string name="menu_settings" msgid="6428291655769260831">"הגדרות"</string>
+    <string name="unsaved" msgid="8704442449002374375">"יש בתמונה הזו שינויים שלא נשמרו."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"האם אתה רוצה לשמור לפני היציאה?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"שמור וצא"</string>
+    <string name="exit" msgid="242642957038770113">"צא"</string>
     <string name="history" msgid="455767361472692409">"היסטוריה"</string>
     <string name="reset" msgid="9013181350779592937">"אפס"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"מצב תמונה נוכחית"</string>
+    <string name="imageState" msgid="8632586742752891968">"אפקטים שהוחלו"</string>
     <string name="compare_original" msgid="8140838959007796977">"השווה"</string>
     <string name="apply_effect" msgid="1218288221200568947">"החל"</string>
     <string name="reset_effect" msgid="7712605581024929564">"אפס"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"ללא"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"מקובע"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"פלנטה קטנה"</string>
     <string name="exposure" msgid="6526397045949374905">"חשיפה"</string>
     <string name="sharpness" msgid="6463103068318055412">"חדות"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"צבע אוטומטי"</string>
     <string name="hue" msgid="6231252147971086030">"גוון"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"צלליות"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"הדגשות"</string>
     <string name="curvesRGB" msgid="915010781090477550">"קימורים"</string>
     <string name="vignette" msgid="934721068851885390">"עמעום קצוות"</string>
     <string name="redeye" msgid="4508883127049472069">"עיניים אדומות"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"צייר"</string>
     <string name="straighten" msgid="26025591664983528">"יישר"</string>
     <string name="crop" msgid="5781263790107850771">"חתוך"</string>
     <string name="rotate" msgid="2796802553793795371">"סובב"</string>
     <string name="mirror" msgid="5482518108154883096">"מראה"</string>
+    <string name="negative" msgid="6998313764388022201">"נגטיב"</string>
     <string name="none" msgid="6633966646410296520">"ללא"</string>
+    <string name="edge" msgid="7036064886242147551">"קצוות"</string>
+    <string name="kmeans" msgid="1630263230946107457">"וורהול"</string>
+    <string name="downsample" msgid="3552938534146980104">"הקטן תמונה"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"אדום"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"ירוק"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"כחול"</string>
+    <string name="draw_style" msgid="2036125061987325389">"סגנון"</string>
+    <string name="draw_size" msgid="4360005386104151209">"גודל"</string>
+    <string name="draw_color" msgid="2119030386987211193">"צבע"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"קווים"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"עט סימון"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"התזה"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"נקה"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"בחר צבע מותאם אישית"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"בחר צבע"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"בחר גודל"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"אישור"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"מקור"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"תוצאה"</string>
 </resources>
diff --git a/res/values-iw/photoeditor_strings.xml b/res/values-iw/photoeditor_strings.xml
deleted file mode 100644
index 33e0c96..0000000
--- a/res/values-iw/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"לא ניתן היה לטעון את התמונה"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"לא ניתן היה לשמור את התמונה שנערכה"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"התמונה שנערכה נשמרה ב-<xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"למחוק שינויים שלא נשמרו?"</string>
-    <string name="yes" msgid="5402582493291792293">"כן"</string>
-    <string name="save" msgid="5516670392524294967">"שמור"</string>
-    <string name="autofix" msgid="1663414996270538748">"תיקון אוטומטי"</string>
-    <string name="crop" msgid="7598378507763334041">"חתוך"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"פיתוח מוצלב"</string>
-    <string name="documentary" msgid="50396326708699797">"דוקומנטרי"</string>
-    <string name="doodle" msgid="1686409894518940990">"ציור"</string>
-    <string name="duotone" msgid="8145893940788467106">"שני גוונים"</string>
-    <string name="facelift" msgid="6205748523156172637">"פנים זוהרות"</string>
-    <string name="facetan" msgid="4412831806626044111">"פנים שזופות"</string>
-    <string name="filllight" msgid="2644989991700022526">"אור מילוי"</string>
-    <string name="fisheye" msgid="6037488646928998921">"עדשת עין הדג"</string>
-    <string name="flip" msgid="2357692401826287480">"הפוך"</string>
-    <string name="grain" msgid="7487585304579789098">"גרעיניות סרט"</string>
-    <string name="grayscale" msgid="7641563843060945228">"שחור-לבן"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"שחור ולבן"</string>
-    <string name="highlight" msgid="3902653944386623972">"הבהרה"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"נגטיב"</string>
-    <string name="posterize" msgid="4139212359561383385">"מצב פוסטר (Posterize)"</string>
-    <string name="redeye" msgid="4958448806369928239">"עין אדומה"</string>
-    <string name="rotate" msgid="6607597269792373083">"סיבוב"</string>
-    <string name="saturation" msgid="8621322012271169931">"רוויה"</string>
-    <string name="sepia" msgid="7978093531824705601">"חום-ספיה"</string>
-    <string name="shadow" msgid="8235188588101973090">"צלליות"</string>
-    <string name="sharpen" msgid="8449662378104403230">"שפר חדות"</string>
-    <string name="straighten" msgid="5217801513491493491">"יישר"</string>
-    <string name="temperature" msgid="1607987938521534517">"חום"</string>
-    <string name="tint" msgid="154435943863418434">"גוון"</string>
-    <string name="vignette" msgid="7648125924662648282">"שוליים דהויים"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"גרור סמנים כדי לחתוך תמונה"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"גרור על גבי התמונה כדי לצייר עליה"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"גרור את התמונה כדי להפוך אותה"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"הקש על עיניים אדומות כדי להסיר אותן"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"גרור את התמונה כדי לסובב אותה"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"גרור את התמונה כדי ליישר אותה"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"אפקטי חשיפה"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"אפקטי צבע"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"אפקטים אמנותיים"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"תקן"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"בטל"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"בצע מחדש"</string>
-</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 153a2b2..f40e655 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"סובב ימינה"</string>
     <string name="no_such_item" msgid="5315144556325243400">"הפריט לא נמצא."</string>
     <string name="edit" msgid="1502273844748580847">"ערוך"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"עריכה פשוטה"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"מעבד בקשות העברה למטמון"</string>
     <string name="caching_label" msgid="4521059045896269095">"מעביר למטמון..."</string>
     <string name="crop_action" msgid="3427470284074377001">"חתוך"</string>
     <string name="trim_action" msgid="703098114452883524">"חתוך"</string>
+    <string name="mute_action" msgid="5296241754753306251">"השתק"</string>
     <string name="set_as" msgid="3636764710790507868">"הגדר בתור"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"לא ניתן להשתיק את הסרטון."</string>
     <string name="video_err" msgid="7003051631792271009">"לא ניתן להפעיל את סרטון הווידאו."</string>
     <string name="group_by_location" msgid="316641628989023253">"לפי מיקום"</string>
     <string name="group_by_time" msgid="9046168567717963573">"לפי שעה"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"אוטומטי"</string>
     <string name="flash_on" msgid="7891556231891837284">"צילום עם פלאש"</string>
     <string name="flash_off" msgid="1445443413822680010">"ללא פלאש"</string>
+    <string name="unknown" msgid="3506693015896912952">"לא ידוע"</string>
     <string name="ffx_original" msgid="372686331501281474">"מקור"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"וינטאג\'"</string>
     <string name="ffx_instant" msgid="726968618715691987">"מיידי"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"אחסון חיצוני לא זמין"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"תצוגה בסרט שקופיות"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"תצוגת רשת"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"תצוגה במסך מלא"</string>
     <string name="trimming" msgid="9122385768369143997">"קיצור"</string>
+    <string name="muting" msgid="5094925919589915324">"משתיק"</string>
     <string name="please_wait" msgid="7296066089146487366">"המתן"</string>
-    <string name="save_into" msgid="4960537214388766062">"שומר באלבום סרטון שקוצר:"</string>
+    <string name="save_into" msgid="9155488424829609229">"שומר את הסרטון ב-<xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"לא ניתן לקצר: סרטון היעד קצר מדי"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"יוצר פנורמה"</string>
     <string name="save" msgid="613976532235060516">"שמור"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"סורק תוכן..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d פריטים נסרקו"</item>
+    <item quantity="one" msgid="4340019444460561648">"פריט %1$d נסרק"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d פריטים נסרקו"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"ממיין..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"הסריקה הסתיימה"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"מייבא..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"אין תוכן זמין לייבוא במכשיר זה."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"לא מחובר אף מכשיר MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"שגיאת מצלמה"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"לא ניתן להתחבר למצלמה."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"המצלמה הושבתה בשל מדיניות אבטחה."</string>
+    <string name="camera_label" msgid="6346560772074764302">"מצלמה"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"מצלמת וידאו"</string>
+    <string name="wait" msgid="8600187532323801552">"המתן..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"טען אחסון USB לפני השימוש במצלמה."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"הכנס כרטיס SD לפני השימוש במצלמה."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"מכין אחסון USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"מכין כרטיס SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"לא ניתן לגשת לאחסון ה-USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"לא ניתן לגשת לכרטיס ה-SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ביטול"</string>
+    <string name="review_ok" msgid="1156261588693116433">"בוצע"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"הקלטה של מעבר זמן"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"בחר מצלמה"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"הקודם"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"חזיתית"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"מיקום אחסון"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"מיקום"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"טיימר לספירה לאחור"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"שנייה אחת"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d שניות"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"צפצף בעת ספירה לאחור"</string>
+    <string name="setting_off" msgid="4480039384202951946">"כבוי"</string>
+    <string name="setting_on" msgid="8602246224465348901">"מופעל"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"איכות סרטון"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"גבוהה"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"נמוכה"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"הילוך מהיר"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"הגדרות מצלמה"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"הגדרות מצלמת וידאו"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"גודל תמונה"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 מגה פיקסל"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 מגה פיקסל"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 מגה פיקסלים"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 מגה פיקסל"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 מגה פיקסלים"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 מגה פיקסלים"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M פיקסל (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3 מגה פיקסלים"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"מגה פיקסל אחד"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"מצב מיקוד"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"אוטומטי"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"אינסוף"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"מאקרו"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"אוטומטי"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"אינסוף"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"מאקרו"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"מצב Flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"מצב מבזק"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"אוטומטי"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"מופעל"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"כבוי"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"מבזק אוטומטי"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"מבזק פועל"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"מבזק כבוי"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"איזון לבן"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"איזון לבן"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"אוטומטי"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"זוהר"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"אור יום"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"פלואורסנט"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"מעונן"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"אוטומטי"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"זוהר"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"אור יום"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"פלואורסנט"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"מעונן"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"מצב נוף"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"אוטומטי"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"פעולה"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"לילה"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"שקיעה"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"מסיבה"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ללא"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"פעולה"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"לילה"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"שקיעה"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"מסיבה"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"טיימר לספירה לאחור"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"טיימר כבוי"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"שנייה אחת"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 שניות"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 שניות"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 שניות"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"לא ניתן לבחירה במצב נוף."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"חשיפה"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"חשיפה"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"מצלמה חזיתית"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"מצלמה אחורית"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"אישור"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"אוזל המקום באמצעי האחסון מסוג USB. שנה את הגדרת האיכות או מחק כמה תמונות או קבצים אחרים."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"השטח בכרטיס SD אוזל. שנה את הגדרת האיכות או מחק חלק מהתמונות או קבצים אחרים."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"הגעת למגבלת הגודל."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"מהר מדי"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"מכין פנורמה"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"לא ניתן לשמור את הפנורמה."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"פנורמה"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"מבצע צילום פנורמה"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"ממתין לפנורמה הקודמת"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"שומר…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"יוצר פנורמה"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"גע כדי להתמקד."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"אפקטים"</string>
+    <string name="effect_none" msgid="3601545724573307541">"ללא"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"כיווץ"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"עיניים גדולות"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"פה גדול"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"פה קטן"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"אף גדול"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"עיניים קטנות"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"בחלל"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"שקיעה"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"הסרטון שלך"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"הנח את המכשיר."\n"צא לרגע מהתמונה."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"גע כדי לצלם תמונה במהלך ההקלטה."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"הקלטת וידאו החלה."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"הקלטת וידאו הופסקה."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"האפשרות לצילום תמונה מסרטון וידאו מושבתת כאשר אפקטים מיוחדים מופעלים."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"נקה אפקטים"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"פרצופים מצחיקים"</string>
+    <string name="effect_background" msgid="6579360207378171022">"רקע"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"לחצן הצמצם."</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"לחצן תפריט"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"התמונה האחרונה"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"מתג המצלמה הקדמית והאחורית"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"בורר מצב מצלמה, וידאו או פנורמה"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"פקדי הגדרות נוספות"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"סגור פקדי הגדרה"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"פקד \'הגדל/הקטן\'"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"הקטן %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"הגדל %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"תיבת סימון %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"עבור לצילום תמונות"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"עבור לווידאו"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"עבור לפנורמה"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"עבור לפנורמה חדשה"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"ביטול בדיקה"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"בדיקה בוצעה"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"הצג צילום חוזר"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"הפעל סרטון"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"השהה סרטון"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"טען סרטון מחדש"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"סרגל הזמן של נגן הסרטונים"</string>
+    <string name="capital_on" msgid="5491353494964003567">"פועל"</string>
+    <string name="capital_off" msgid="7231052688467970897">"כבוי"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"כבוי"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 שנייה"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"שנייה אחת"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 שניות"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 דקה"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"דקה אחת"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 דקות"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 שעה"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"שעה אחת"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"שעתיים"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 שעות"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 שעות"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"שניות"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"דקות"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"שעות"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"בוצע"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"הגדר מרווח זמן"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"תכונת ההילוך המהיר כבויה. הפעל אותה כדי להגדיר מרווח זמן."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"טיימר הספירה לאחור כבוי. הפעל אותו כדי לספור לאחור לפני צילום תמונה."</string>
+    <string name="set_duration" msgid="5578035312407161304">"הגדר משך זמן בשניות"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"סופר לאחור עד לצילום תמונה"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"האם לזכור מיקומי תמונות?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"מתייג את התמונות והסרטונים שלך לציון המקומות שבהם צולמו."\n\n"יישומים אחרים יכולים לגשת למידע זה, כולל תמונות שנשמרו."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"לא, תודה"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"כן"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"מצלמה"</string>
+    <string name="menu_search" msgid="7580008232297437190">"חיפוש"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"תמונות"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"אלבומים"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"אפשרויות נוספות"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"הגדרות"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"תמונה %1$d"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d תמונות"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ja/filtershow_strings.xml b/res/values-ja/filtershow_strings.xml
index aaf301c..f7efe4a 100644
--- a/res/values-ja/filtershow_strings.xml
+++ b/res/values-ja/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"画像を読み込めません"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"壁紙を設定しています"</string>
+    <string name="download_failure" msgid="5923323939788582895">"画像をダウンロードできませんでした。ネットワークを使用できません。"</string>
     <string name="original" msgid="3524493791230430897">"元の画像"</string>
     <string name="borders" msgid="2067345080568684614">"境界"</string>
-    <string name="done" msgid="3112344807927554662">"完了"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"元に戻す"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"やり直し"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"履歴を表示する"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"履歴を表示しない"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"画像ステータスを表示"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"画像ステータスを非表示"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"適用済みの効果を表示"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"適用済みの効果を非表示"</string>
     <string name="menu_settings" msgid="6428291655769260831">"設定"</string>
+    <string name="unsaved" msgid="8704442449002374375">"この画像には保存されていない変更があります。"</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"終了する前に保存しますか?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"保存して終了"</string>
+    <string name="exit" msgid="242642957038770113">"終了"</string>
     <string name="history" msgid="455767361472692409">"履歴"</string>
     <string name="reset" msgid="9013181350779592937">"リセット"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"現在の画像ステータス"</string>
+    <string name="imageState" msgid="8632586742752891968">"適用済みの効果"</string>
     <string name="compare_original" msgid="8140838959007796977">"比較"</string>
     <string name="apply_effect" msgid="1218288221200568947">"適用"</string>
     <string name="reset_effect" msgid="7712605581024929564">"リセット"</string>
@@ -49,26 +52,46 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"なし"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"固定"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"小さな惑星"</string>
     <string name="exposure" msgid="6526397045949374905">"露出"</string>
     <string name="sharpness" msgid="6463103068318055412">"シャープネス"</string>
     <string name="contrast" msgid="2310908487756769019">"コントラスト"</string>
     <string name="vibrance" msgid="3326744578577835915">"自然な彩度"</string>
     <string name="saturation" msgid="7026791551032438585">"彩度"</string>
-    <string name="bwfilter" msgid="8927492494576933793">"モノクロフィルタ"</string>
+    <string name="bwfilter" msgid="8927492494576933793">"モノクロ"</string>
     <string name="wbalance" msgid="6346581563387083613">"自動色補正"</string>
     <string name="hue" msgid="6231252147971086030">"色彩"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"影"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"ハイライト"</string>
     <string name="curvesRGB" msgid="915010781090477550">"カーブ"</string>
     <string name="vignette" msgid="934721068851885390">"周辺減光"</string>
     <string name="redeye" msgid="4508883127049472069">"赤目処理"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"描画"</string>
     <string name="straighten" msgid="26025591664983528">"傾き調整"</string>
     <string name="crop" msgid="5781263790107850771">"トリミング"</string>
     <string name="rotate" msgid="2796802553793795371">"回転"</string>
     <string name="mirror" msgid="5482518108154883096">"鏡"</string>
+    <string name="negative" msgid="6998313764388022201">"白黒反転"</string>
     <string name="none" msgid="6633966646410296520">"なし"</string>
+    <string name="edge" msgid="7036064886242147551">"エッジ"</string>
+    <string name="kmeans" msgid="1630263230946107457">"ウォーホル"</string>
+    <string name="downsample" msgid="3552938534146980104">"縮小"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"赤"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"緑"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"青"</string>
+    <string name="draw_style" msgid="2036125061987325389">"スタイル"</string>
+    <string name="draw_size" msgid="4360005386104151209">"サイズ"</string>
+    <string name="draw_color" msgid="2119030386987211193">"色"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"線"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"マーカー"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"スパッタリング"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"消去"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"ユーザー定義の色を選択"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"色の選択"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"サイズの選択"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"元の画像"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"結果"</string>
 </resources>
diff --git a/res/values-ja/photoeditor_strings.xml b/res/values-ja/photoeditor_strings.xml
deleted file mode 100644
index f0e35b3..0000000
--- a/res/values-ja/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"フォトスタジオ"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"画像を読み込めませんでした"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"編集した画像を保存できませんでした"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"編集した画像を<xliff:g id="FOLDER_NAME">%s</xliff:g>に保存しました"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"保存されていない変更を破棄しますか?"</string>
-    <string name="yes" msgid="5402582493291792293">"はい"</string>
-    <string name="save" msgid="5516670392524294967">"保存"</string>
-    <string name="autofix" msgid="1663414996270538748">"自動修正"</string>
-    <string name="crop" msgid="7598378507763334041">"トリミング"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"クロスプロセス"</string>
-    <string name="documentary" msgid="50396326708699797">"ドキュメンタリー"</string>
-    <string name="doodle" msgid="1686409894518940990">"落書き"</string>
-    <string name="duotone" msgid="8145893940788467106">"デュオトーン"</string>
-    <string name="facelift" msgid="6205748523156172637">"明るい肌色"</string>
-    <string name="facetan" msgid="4412831806626044111">"日焼けした肌色"</string>
-    <string name="filllight" msgid="2644989991700022526">"明るさ調整"</string>
-    <string name="fisheye" msgid="6037488646928998921">"魚眼レンズ"</string>
-    <string name="flip" msgid="2357692401826287480">"反転"</string>
-    <string name="grain" msgid="7487585304579789098">"フィルムグレイン"</string>
-    <string name="grayscale" msgid="7641563843060945228">"モノクロ"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"白黒"</string>
-    <string name="highlight" msgid="3902653944386623972">"ハイライト"</string>
-    <string name="lomoish" msgid="1270032154357186736">"LOMO"</string>
-    <string name="negative" msgid="1985508917342811252">"白黒反転"</string>
-    <string name="posterize" msgid="4139212359561383385">"ポスタライズ"</string>
-    <string name="redeye" msgid="4958448806369928239">"赤目処理"</string>
-    <string name="rotate" msgid="6607597269792373083">"回転"</string>
-    <string name="saturation" msgid="8621322012271169931">"彩度"</string>
-    <string name="sepia" msgid="7978093531824705601">"セピア"</string>
-    <string name="shadow" msgid="8235188588101973090">"シャドウ"</string>
-    <string name="sharpen" msgid="8449662378104403230">"シャープ"</string>
-    <string name="straighten" msgid="5217801513491493491">"傾き調整"</string>
-    <string name="temperature" msgid="1607987938521534517">"暖色"</string>
-    <string name="tint" msgid="154435943863418434">"色合い"</string>
-    <string name="vignette" msgid="7648125924662648282">"周辺減光"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"写真をトリミングするにはマーカーをドラッグ"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"画像の上に書き込みます"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"画像をめくるにはドラッグします"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"赤目を補正するには赤目部分をタップします"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"画像を回転させるにはドラッグします"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"画像をまっすぐにするにはドラッグします"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"露出効果"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"色効果"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"芸術的効果"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"修正"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"元に戻す"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"やり直し"</string>
-</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index b360f60..ab7ca84 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"右に回転"</string>
     <string name="no_such_item" msgid="5315144556325243400">"アイテムが見つかりませんでした。"</string>
     <string name="edit" msgid="1502273844748580847">"編集"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"シンプルな編集"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"キャッシュリクエストを処理しています"</string>
     <string name="caching_label" msgid="4521059045896269095">"キャッシュ中..."</string>
     <string name="crop_action" msgid="3427470284074377001">"トリミング"</string>
     <string name="trim_action" msgid="703098114452883524">"トリミング"</string>
+    <string name="mute_action" msgid="5296241754753306251">"ミュート"</string>
     <string name="set_as" msgid="3636764710790507868">"登録"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"動画をミュートできません。"</string>
     <string name="video_err" msgid="7003051631792271009">"動画を再生できません。"</string>
     <string name="group_by_location" msgid="316641628989023253">"地域別"</string>
     <string name="group_by_time" msgid="9046168567717963573">"時間別"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"オート"</string>
     <string name="flash_on" msgid="7891556231891837284">"フラッシュON"</string>
     <string name="flash_off" msgid="1445443413822680010">"フラッシュOFF"</string>
+    <string name="unknown" msgid="3506693015896912952">"不明"</string>
     <string name="ffx_original" msgid="372686331501281474">"オリジナル"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"ヴィンテージ"</string>
     <string name="ffx_instant" msgid="726968618715691987">"インスタント"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"利用できる外部ストレージがありません"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"フィルムストリップ表示"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"グリッド表示"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"全画面表示"</string>
     <string name="trimming" msgid="9122385768369143997">"トリミング"</string>
+    <string name="muting" msgid="5094925919589915324">"ミュートしています"</string>
     <string name="please_wait" msgid="7296066089146487366">"お待ちください"</string>
-    <string name="save_into" msgid="4960537214388766062">"トリミングした動画を次のアルバムに保存中:"</string>
+    <string name="save_into" msgid="9155488424829609229">"動画を<xliff:g id="ALBUM_NAME">%1$s</xliff:g>に保存しています…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"トリミングできません: 動画が短すぎます"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"パノラマをレンダリング中"</string>
     <string name="save" msgid="613976532235060516">"保存"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"コンテンツをスキャンしています..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d件のアイテムをスキャン済み"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d件のアイテムをスキャン済み"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d件のアイテムをスキャン済み"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"並べ替えています..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"スキャンが終了しました"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"インポートしています..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"この端末にインポートできるコンテンツがありません。"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"接続されているMTPデバイスがありません"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"カメラエラー"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"カメラに接続できません。"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"カメラはセキュリティポリシーにより無効になっています。"</string>
+    <string name="camera_label" msgid="6346560772074764302">"カメラ"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"ビデオ録画"</string>
+    <string name="wait" msgid="8600187532323801552">"お待ちください..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"カメラを使用する前にUSBストレージをマウントしてください。"</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"カメラを使用する前にSDカードを挿入してください。"</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USBストレージの準備中..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SDカードの準備中..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USBストレージにアクセスできませんでした。"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SDカードにアクセスできませんでした。"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"キャンセル"</string>
+    <string name="review_ok" msgid="1156261588693116433">"完了"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"低速度撮影"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"カメラを選択"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"背面"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"前面"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"位置情報を記録する"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"現在地"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"カウントダウンタイマー"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1秒"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d秒"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"タイマー時にビープ音"</string>
+    <string name="setting_off" msgid="4480039384202951946">"OFF"</string>
+    <string name="setting_on" msgid="8602246224465348901">"ON"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"動画の画質"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"高"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"低"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"低速度撮影"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"カメラ設定"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"ビデオ録画設定"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"画像サイズ"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13Mピクセル"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8Mピクセル"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5メガピクセル"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4Mピクセル"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3メガピクセル"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2メガピクセル"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2Mピクセル(16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3メガピクセル"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1メガピクセル"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"フォーカスモード"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"オート"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"無限遠"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"マクロ"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"オート"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"無限遠"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"マクロ"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"フラッシュモード"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"フラッシュモード"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"オート"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"ON"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"OFF"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"フラッシュオート"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"フラッシュON"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"フラッシュOFF"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"ホワイトバランス"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"ホワイトバランス"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"オート"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"白熱灯"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"昼光"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"蛍光灯"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"曇り"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"オート"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"白熱灯"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"昼光"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"蛍光灯"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"曇り"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"撮影モード"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"オート"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"スポーツ"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"夜景"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"夕焼け"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"パーティー"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"なし"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"スポーツ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"夜景"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"夕焼け"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"パーティー"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"カウントダウンタイマー"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"タイマーOFF"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1秒"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3秒"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10秒"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15秒"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"撮影モードでは選択できません。"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"露出"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"露出"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"前面カメラ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"背面カメラ"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USBストレージの空き領域が少なくなっています。画質設定を変更するか、画像など一部のファイルを削除してください。"</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SDカードの空き領域が少なくなっています。画質設定を変更するか、画像などのファイルを一部削除してください。"</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"サイズ制限に達しました。"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"速すぎます"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"パノラマを準備しています"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"パノラマを保存できませんでした。"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"パノラマ"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"パノラマを撮影中"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"前のパノラマを待機中"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"保存中..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"パノラマをレンダリング中"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"タップしてフォーカスします。"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"効果"</string>
+    <string name="effect_none" msgid="3601545724573307541">"なし"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"スクイーズ"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"大きな目"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"大きな口"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"小さな口"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"大きな鼻"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"小さな目"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"宇宙空間"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"夕焼け"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"あなたの動画"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"端末をセッティングします。"\n"少しの間フレームの外に出ます。"</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"録画中にタップして静止画を撮影できます。"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"録画を開始しました。"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"録画を停止しました。"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"動画スナップショットは特殊効果がONのときは無効です。"</string>
+    <string name="clear_effects" msgid="5485339175014139481">"効果設定をクリア"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"変な顔"</string>
+    <string name="effect_background" msgid="6579360207378171022">"背景"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"シャッターボタン"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"メニューボタン"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"最近の写真"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"前後カメラの切り替え"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"カメラ、ビデオ、パノラマの切り替え"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"その他の設定コントロール"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"設定コントロールを閉じる"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"ズームコントロール"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"-%1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"+%1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$sチェックボックス"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"カメラに切り替え"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"動画に切り替え"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"パノラマに切り替え"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"新しいパノラマに切り替える"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"レビュー - キャンセル"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"レビュー - 完了"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"撮り直しを確認"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"動画を再生"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"動画を一時停止"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"動画を再読み込み"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"動画プレーヤーのタイムバー"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ON"</string>
+    <string name="capital_off" msgid="7231052688467970897">"OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5分"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1分"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5分"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2分"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5分"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3分"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4分"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5分"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6分"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10分"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12分"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15分"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24分"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15時間"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24時間"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"秒"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"分"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"時間"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"完了"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"間隔を設定"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"低速度撮影機能がOFFになっています。間隔を設定するにはONにしてください。"</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"カウントダウンタイマーがオフです。画像を撮影するまでカウントダウンするには、このタイマーをオンにしてください。"</string>
+    <string name="set_duration" msgid="5578035312407161304">"設定時間(秒数)"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"画像を撮影するまでカウントダウンします"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"撮影場所を記録しますか?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"画像や動画に撮影場所のタグを付けることができます。"\n\n"他のアプリから、保存された画像とともに撮影場所の情報にもアクセスできるようになります。"</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"いいえ"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"はい"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"カメラ"</string>
+    <string name="menu_search" msgid="7580008232297437190">"検索"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"写真"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"アルバム"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"その他のオプション"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"設定"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d枚の画像"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d枚の画像"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ko/filtershow_strings.xml b/res/values-ko/filtershow_strings.xml
index 4cb7e99..933aa68 100644
--- a/res/values-ko/filtershow_strings.xml
+++ b/res/values-ko/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"이미지를 로드할 수 없습니다."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"배경화면 설정 중"</string>
+    <string name="download_failure" msgid="5923323939788582895">"네트워크를 사용할 수 없기 때문에 사진을 다운로드할 수 없습니다."</string>
     <string name="original" msgid="3524493791230430897">"원본"</string>
     <string name="borders" msgid="2067345080568684614">"테두리"</string>
-    <string name="done" msgid="3112344807927554662">"완료"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"실행취소"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"다시실행"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"기록 표시"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"기록 숨기기"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"이미지 상태 표시"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"이미지 상태 숨기기"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"적용된 효과 표시"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"적용된 효과 숨기기"</string>
     <string name="menu_settings" msgid="6428291655769260831">"설정"</string>
+    <string name="unsaved" msgid="8704442449002374375">"이 이미지에 저장하지 않은 변경사항이 있습니다."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"저장하고 나서 종료하시겠습니까?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"저장 및 종료"</string>
+    <string name="exit" msgid="242642957038770113">"종료"</string>
     <string name="history" msgid="455767361472692409">"기록"</string>
     <string name="reset" msgid="9013181350779592937">"초기화"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"현재 이미지 상태"</string>
+    <string name="imageState" msgid="8632586742752891968">"적용된 효과"</string>
     <string name="compare_original" msgid="8140838959007796977">"비교하기"</string>
     <string name="apply_effect" msgid="1218288221200568947">"적용"</string>
     <string name="reset_effect" msgid="7712605581024929564">"초기화"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"선택 안 함"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"고정"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"작은 행성"</string>
     <string name="exposure" msgid="6526397045949374905">"노출"</string>
     <string name="sharpness" msgid="6463103068318055412">"선명도"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"자동 색상"</string>
     <string name="hue" msgid="6231252147971086030">"색조"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"그림자"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"하이라이트"</string>
     <string name="curvesRGB" msgid="915010781090477550">"곡선"</string>
     <string name="vignette" msgid="934721068851885390">"비네트"</string>
     <string name="redeye" msgid="4508883127049472069">"적목현상 없애기"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"그리기"</string>
     <string name="straighten" msgid="26025591664983528">"수평조절"</string>
     <string name="crop" msgid="5781263790107850771">"자르기"</string>
     <string name="rotate" msgid="2796802553793795371">"회전"</string>
     <string name="mirror" msgid="5482518108154883096">"거울"</string>
+    <string name="negative" msgid="6998313764388022201">"네거티브"</string>
     <string name="none" msgid="6633966646410296520">"선택 안 함"</string>
+    <string name="edge" msgid="7036064886242147551">"가장자리"</string>
+    <string name="kmeans" msgid="1630263230946107457">"워홀"</string>
+    <string name="downsample" msgid="3552938534146980104">"다운샘플"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"빨간색"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"녹색"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"파란색"</string>
+    <string name="draw_style" msgid="2036125061987325389">"스타일"</string>
+    <string name="draw_size" msgid="4360005386104151209">"크기"</string>
+    <string name="draw_color" msgid="2119030386987211193">"색상"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"선"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"마커"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"튀기기"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"삭제"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"맞춤 색상 선택"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"색상 선택"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"크기 선택"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"확인"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"원본"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"결과"</string>
 </resources>
diff --git a/res/values-ko/photoeditor_strings.xml b/res/values-ko/photoeditor_strings.xml
deleted file mode 100644
index 1d192b7..0000000
--- a/res/values-ko/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"사진 스튜디오"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"사진을 로드할 수 없습니다."</string>
-    <string name="saving_failure" msgid="8229491575433743974">"수정한 사진을 저장할 수 없습니다."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"수정한 사진이 <xliff:g id="FOLDER_NAME">%s</xliff:g>에 저장되었습니다."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"저장되지 않은 변경사항을 삭제하시겠습니까?"</string>
-    <string name="yes" msgid="5402582493291792293">"예"</string>
-    <string name="save" msgid="5516670392524294967">"저장"</string>
-    <string name="autofix" msgid="1663414996270538748">"자동 보정"</string>
-    <string name="crop" msgid="7598378507763334041">"자르기"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"크로스 프로세스"</string>
-    <string name="documentary" msgid="50396326708699797">"다큐멘터리"</string>
-    <string name="doodle" msgid="1686409894518940990">"낙서"</string>
-    <string name="duotone" msgid="8145893940788467106">"더블톤"</string>
-    <string name="facelift" msgid="6205748523156172637">"뽀샵 효과"</string>
-    <string name="facetan" msgid="4412831806626044111">"선탠 효과"</string>
-    <string name="filllight" msgid="2644989991700022526">"밝게"</string>
-    <string name="fisheye" msgid="6037488646928998921">"어안렌즈"</string>
-    <string name="flip" msgid="2357692401826287480">"방향 바꾸기"</string>
-    <string name="grain" msgid="7487585304579789098">"거친 필름"</string>
-    <string name="grayscale" msgid="7641563843060945228">"흑백"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"흑백"</string>
-    <string name="highlight" msgid="3902653944386623972">"하이라이트"</string>
-    <string name="lomoish" msgid="1270032154357186736">"로모"</string>
-    <string name="negative" msgid="1985508917342811252">"네거티브"</string>
-    <string name="posterize" msgid="4139212359561383385">"포스터"</string>
-    <string name="redeye" msgid="4958448806369928239">"적목현상"</string>
-    <string name="rotate" msgid="6607597269792373083">"회전"</string>
-    <string name="saturation" msgid="8621322012271169931">"채도"</string>
-    <string name="sepia" msgid="7978093531824705601">"세피아"</string>
-    <string name="shadow" msgid="8235188588101973090">"그림자"</string>
-    <string name="sharpen" msgid="8449662378104403230">"선명하게"</string>
-    <string name="straighten" msgid="5217801513491493491">"똑바르게"</string>
-    <string name="temperature" msgid="1607987938521534517">"따뜻하게"</string>
-    <string name="tint" msgid="154435943863418434">"농담"</string>
-    <string name="vignette" msgid="7648125924662648282">"비네트"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"아이콘을 드래그하여 사진 자르기"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"사진에 그림을 그려 낙서하기"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"사진을 드래그하여 방향 바꾸기"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"적목현상을 없애려면 눈을 탭하세요."</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"드래그하여 사진 회전"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"사진을 드래그하여 반듯하게 하기"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"노출 효과"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"색상 효과"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"꾸밈 효과"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"보정"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"실행취소"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"다시실행"</string>
-</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1593867..c09d11c 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"오른쪽으로 회전"</string>
     <string name="no_such_item" msgid="5315144556325243400">"항목을 찾을 수 없습니다."</string>
     <string name="edit" msgid="1502273844748580847">"수정"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"간편 수정"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"캐시 요청을 처리하는 중"</string>
     <string name="caching_label" msgid="4521059045896269095">"캐시하는 중..."</string>
     <string name="crop_action" msgid="3427470284074377001">"자르기"</string>
     <string name="trim_action" msgid="703098114452883524">"다듬기"</string>
+    <string name="mute_action" msgid="5296241754753306251">"음소거"</string>
     <string name="set_as" msgid="3636764710790507868">"다음으로 설정"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"동영상을 음소거할 수 없습니다."</string>
     <string name="video_err" msgid="7003051631792271009">"동영상을 재생할 수 없습니다."</string>
     <string name="group_by_location" msgid="316641628989023253">"위치별"</string>
     <string name="group_by_time" msgid="9046168567717963573">"시간별"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"자동"</string>
     <string name="flash_on" msgid="7891556231891837284">"플래시 터짐"</string>
     <string name="flash_off" msgid="1445443413822680010">"플래시 없음"</string>
+    <string name="unknown" msgid="3506693015896912952">"알 수 없음"</string>
     <string name="ffx_original" msgid="372686331501281474">"원본"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"빈티지"</string>
     <string name="ffx_instant" msgid="726968618715691987">"인스턴트"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"사용할 수 있는 외부 저장소 없음"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"슬라이드 보기"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"바둑판식 보기"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"전체화면 보기"</string>
     <string name="trimming" msgid="9122385768369143997">"자르기"</string>
+    <string name="muting" msgid="5094925919589915324">"음소거 중"</string>
     <string name="please_wait" msgid="7296066089146487366">"잠시 기다려 주세요."</string>
-    <string name="save_into" msgid="4960537214388766062">"잘라낸 동영상을 앨범에 저장 중:"</string>
+    <string name="save_into" msgid="9155488424829609229">"<xliff:g id="ALBUM_NAME">%1$s</xliff:g>에 동영상 저장 중 …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"잘라낼 수 없음 : 동영상이 너무 짧음"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"파노라마 렌더링"</string>
     <string name="save" msgid="613976532235060516">"저장"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"콘텐츠를 스캔하는 중..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d개 항목을 스캔함"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d개 항목을 스캔함"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d개 항목을 스캔함"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"정렬 중..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"스캔 완료"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"가져오는 중..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"이 기기로 가져올 수 있는 콘텐츠가 없습니다."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"연결된 MTP 기기가 없습니다."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"카메라 오류"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"카메라에 연결할 수 없습니다."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"보안 정책으로 인해 카메라 사용이 중지되었습니다."</string>
+    <string name="camera_label" msgid="6346560772074764302">"카메라"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"캠코더"</string>
+    <string name="wait" msgid="8600187532323801552">"잠시 기다려 주세요..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"카메라를 사용하기 전에 USB 저장소를 마운트하세요."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"카메라를 사용하기 전에 SD 카드를 삽입하세요."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB 저장소 준비 중…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD 카드 준비중..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USB 저장소에 액세스하지 못했습니다."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SD 카드에 액세스하지 못했습니다."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"취소"</string>
+    <string name="review_ok" msgid="1156261588693116433">"완료"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"저속 촬영"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"카메라 선택"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"후방"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"전방"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"위치 저장"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"위치"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"카운트다운 타이머"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1초"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d초"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"카운트다운하는 동안 신호음 울리기"</string>
+    <string name="setting_off" msgid="4480039384202951946">"OFF"</string>
+    <string name="setting_on" msgid="8602246224465348901">"ON"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"동영상 화질"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"고화질"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"저화질"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"시간 경과"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"카메라 설정"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"캠코더 설정"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"사진 크기"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"1300만 화소"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M 픽셀"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M 픽셀"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M 픽셀"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M 픽셀"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M 픽셀"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M 픽셀"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"초점 모드"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"자동"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"무한"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"매크로"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"자동"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"무한"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"매크로"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"플래시 모드"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"플래시 모드"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"자동"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"ON"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"OFF"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"자동 플래시"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"플래시 사용"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"플래시 사용 안함"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"화이트 밸런스"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"화이트 밸런스"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"자동"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"백열"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"일광"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"형광"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"흐림"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"자동"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"백열"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"일광"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"형광"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"흐림"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"장면 모드"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"자동"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"동작"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"야간"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"일몰"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"파티"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"없음"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"작업"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"밤"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"일몰"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"파티"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"카운트다운 타이머"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"타이머 중지"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1초"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3초"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10초"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15초"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"장면 모드에서 선택할 수 없습니다."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"노출"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"노출"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"전방 카메라"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"후방 카메라"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"확인"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB 저장장치의 공간이 부족합니다. 화질 설정을 변경하거나 일부 이미지 또는 기타 파일을 삭제하세요."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD 카드의 공간이 부족합니다. 화질 설정을 변경하거나 일부 이미지 또는 기타 파일을 삭제하세요."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"크기 한도에 도달했습니다."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"너무 빠름"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"파노라마 준비 중"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"파노라마를 저장하지 못했습니다."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"파노라마"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"파노라마 캡처 중"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"이전 파노라마가 완료되기를 기다리는 중"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"저장 중…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"파노라마 렌더링"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"초점을 맞추려면 터치하세요."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"효과"</string>
+    <string name="effect_none" msgid="3601545724573307541">"없음"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"찌그러뜨리기"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"눈 크게"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"입 크게"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"입 작게"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"코 크게"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"눈 작게"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"우주"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"일몰"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"내 동영상"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"기기를 내려놓습니다."\n"잠시 동안 시야를 벗어납니다."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"녹화 중에 사진을 찍으려면 터치하세요."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"동영상 녹화가 시작되었습니다."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"동영상 녹화가 중지되었습니다."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"특수 효과가 설정되어 있으면 동영상 스냅샷이 사용 중지됩니다."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"효과 제거"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"웃긴 얼굴"</string>
+    <string name="effect_background" msgid="6579360207378171022">"배경"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"셔터 버튼"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"메뉴 버튼"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"최근 사진"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"전방 및 후방 카메라 전환"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"카메라, 동영상 또는 파노라마 선택기"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"설정 컨트롤 더보기"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"설정 컨트롤 닫기"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"확대/축소 컨트롤"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"%1$s 축소"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"%1$s 확대"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s 확인란"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"사진으로 전환"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"동영상으로 전환"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"파노라마로 전환"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"새 파노라마로 전환"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"리뷰 취소"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"리뷰 완료"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"다시 찍기 검토"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"동영상 재생"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"동영상 일지중지"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"동영상 새로고침"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"동영상 플레이어 시간 표시 막대"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ON"</string>
+    <string name="capital_off" msgid="7231052688467970897">"OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"사용 안함"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5초"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1초"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5초"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2초"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5초"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3초"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4초"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5초"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6초"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10초"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12초"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15초"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24초"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5분"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1분"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5분"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2분"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5분"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3분"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4분"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5분"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6분"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10분"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12분"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15분"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24분"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15시간"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24시간"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"초"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"분"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"시간"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"완료"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"시간 간격 설정"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"시간 경과 기능을 사용하지 않고 있습니다. 시간 간격을 설정하려면 \'사용\'으로 변경해 주세요."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"카운트다운 타이머가 꺼져 있습니다. 사진을 찍기 전에 카운트다운을 켜세요."</string>
+    <string name="set_duration" msgid="5578035312407161304">"시간 설정(초)"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"사진을 찍으려면 카운트다운하세요."</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"사진 위치를 기록하시겠습니까?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"촬영한 위치로 사진과 동영상에 태그를 지정하세요."\n\n"다른 앱이 저장된 이미지와 더불어 이 정보에 액세스할 수 있습니다."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"아니요"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"예"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"카메라"</string>
+    <string name="menu_search" msgid="7580008232297437190">"검색"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"사진"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"앨범"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"옵션 더보기"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"설정"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"사진 %1$d장"</item>
+    <item quantity="other" msgid="3813306834113858135">"사진 %1$d장"</item>
+  </plurals>
 </resources>
diff --git a/res/values-land/dimensions.xml b/res/values-land/dimensions.xml
index b5c5ed3..3eae856 100644
--- a/res/values-land/dimensions.xml
+++ b/res/values-land/dimensions.xml
@@ -16,4 +16,5 @@
 <resources>
     <!-- for manage cache bar -->
     <dimen name="manage_cache_bottom_height">39dp</dimen>
+    <dimen name="capture_top_margin">0dip</dimen>
 </resources>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
index c10362e..6ca7e91 100644
--- a/res/values-land/styles.xml
+++ b/res/values-land/styles.xml
@@ -19,4 +19,57 @@
     <style name="ActionBarTwoLinePrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
         <item name="android:textSize">14sp</item>
     </style>
+
+    <!-- Camera resources below -->
+
+    <style name="ReviewControlIcon">
+        <item name="android:layout_height">@dimen/switcher_size</item>
+        <item name="android:layout_width">@dimen/switcher_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:background">@drawable/bg_pressed</item>
+    </style>
+    <style name="SettingPopupWindow">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:layout_marginRight">@dimen/setting_popup_right_margin</item>
+        <item name="android:visibility">gone</item>
+    </style>
+    <style name="PopupTitleText">
+        <item name="android:textSize">@dimen/popup_title_text_size</item>
+        <item name="android:layout_gravity">left|center_vertical</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">@color/popup_title_color</item>
+        <item name="android:layout_marginLeft">10dp</item>
+        <item name="android:paddingLeft">16dp</item>
+    </style>
+    <style name="ViewfinderLabelLayout">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginLeft">13dp</item>
+        <item name="android:layout_marginRight">@dimen/indicator_bar_width</item>
+        <item name="android:layout_marginBottom">13dp</item>
+        <item name="android:layout_marginTop">13dp</item>
+    </style>
+        <style name="PanoViewHorizontalBar">
+        <item name="android:background">#000000</item>
+        <item name="android:alpha">1.0</item>
+        <item name="android:layout_height">0dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_weight">1.5</item>
+    </style>
+    <style name="SettingPopupWindow_xlarge">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:layout_alignParentRight">true</item>
+        <item name="android:layout_marginRight">@dimen/setting_popup_right_margin</item>
+        <item name="android:visibility">gone</item>
+    </style>
+
 </resources>
diff --git a/res/values-large-hdpi/drawable.xml b/res/values-large-hdpi/drawable.xml
new file mode 100644
index 0000000..b810347
--- /dev/null
+++ b/res/values-large-hdpi/drawable.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <item name="btn_video_shutter_recording_holo" type="drawable">@drawable/btn_video_shutter_recording_holo_large</item>
+    <item name="btn_video_shutter_recording_pressed_holo" type="drawable">@drawable/btn_video_shutter_recording_pressed_holo_large</item>
+    <item name="ic_effects_holo_light" type="drawable">@drawable/ic_effects_holo_light_large</item>
+    <item name="ic_pan_border_fast" type="drawable">@drawable/ic_pan_border_fast_large</item>
+    <item name="ic_pan_left_indicator_fast" type="drawable">@drawable/ic_pan_left_indicator_fast_large</item>
+    <item name="ic_pan_left_indicator" type="drawable">@drawable/ic_pan_left_indicator_large</item>
+    <item name="ic_pan_progression" type="drawable">@drawable/ic_pan_progression_large</item>
+    <item name="ic_pan_right_indicator_fast" type="drawable">@drawable/ic_pan_right_indicator_fast_large</item>
+    <item name="ic_pan_right_indicator" type="drawable">@drawable/ic_pan_right_indicator_large</item>
+    <item name="ic_scn_holo_light" type="drawable">@drawable/ic_scn_holo_light_large</item>
+    <item name="ic_snapshot_border" type="drawable">@drawable/ic_snapshot_border_large</item>
+    <item name="ic_switch_photo_facing_holo_light" type="drawable">@drawable/ic_switch_photo_facing_holo_light_large</item>
+    <item name="ic_switch_video_facing_holo_light" type="drawable">@drawable/ic_switch_video_facing_holo_light_large</item>
+    <item name="ic_timelapse_none" type="drawable">@drawable/ic_timelapse_none_large</item>
+    <item name="list_divider" type="drawable">@drawable/list_divider_large</item>
+</resources>
diff --git a/res/values-large/dimens.xml b/res/values-large/dimens.xml
new file mode 100644
index 0000000..d663a9d
--- /dev/null
+++ b/res/values-large/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <dimen name="setting_popup_right_margin">@dimen/setting_popup_right_margin_large</dimen>
+    <dimen name="setting_row_height">@dimen/setting_row_height_large</dimen>
+    <dimen name="setting_popup_window_width">@dimen/setting_popup_window_width_large</dimen>
+    <dimen name="setting_item_icon_width">@dimen/setting_item_icon_width_large</dimen>
+    <dimen name="onscreen_indicators_height">@dimen/onscreen_indicators_height_large</dimen>
+</resources>
diff --git a/res/values-large/filtershow_values.xml b/res/values-large/filtershow_values.xml
new file mode 100644
index 0000000..1098ee0
--- /dev/null
+++ b/res/values-large/filtershow_values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- Specify the screen orientation -->
+    <bool name="only_use_portrait">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-lt/filtershow_strings.xml b/res/values-lt/filtershow_strings.xml
index 1296e97..d41d751 100644
--- a/res/values-lt/filtershow_strings.xml
+++ b/res/values-lt/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nepavyksta įkelti vaizdo!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Nustatomas ekrano fonas"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Nepavyko atsisiųsti nuotraukos. Tinklas nepasiekiamas."</string>
     <string name="original" msgid="3524493791230430897">"Originalas"</string>
     <string name="borders" msgid="2067345080568684614">"Kraštinės"</string>
-    <string name="done" msgid="3112344807927554662">"Atlikta"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Anuliuoti"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Grąžinti"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Rodyti istoriją"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Slėpti istoriją"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Rodyti vaizdo būseną"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Slėpti vaizdo būseną"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Rodyti pritaikytus efektus"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Slėpti pritaikytus efektus"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Nustatymai"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Yra neišsaugotų šio vaizdo pakeitimų."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Ar norite išsaugoti prieš išeidami?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Išsaugoti ir išeiti"</string>
+    <string name="exit" msgid="242642957038770113">"Išeiti"</string>
     <string name="history" msgid="455767361472692409">"Istorija"</string>
     <string name="reset" msgid="9013181350779592937">"Nust. iš naujo"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Dabartinė vaizdo būsena"</string>
+    <string name="imageState" msgid="8632586742752891968">"Pritaikyti efektai"</string>
     <string name="compare_original" msgid="8140838959007796977">"Palyginti"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Taikyti"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Nust. iš naujo"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nėra"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fiksuota"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Maža plan."</string>
     <string name="exposure" msgid="6526397045949374905">"Išlaikymas"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ryškumas"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. spalva"</string>
     <string name="hue" msgid="6231252147971086030">"Atspalvis"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Šešėliai"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Paryškinimai"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kreivės"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjetė"</string>
     <string name="redeye" msgid="4508883127049472069">"Raudonos akys"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Piešti"</string>
     <string name="straighten" msgid="26025591664983528">"Ištiesinti"</string>
     <string name="crop" msgid="5781263790107850771">"Apkarpyti"</string>
     <string name="rotate" msgid="2796802553793795371">"Sukti"</string>
     <string name="mirror" msgid="5482518108154883096">"Veidrod. atsp."</string>
+    <string name="negative" msgid="6998313764388022201">"Negatyvas"</string>
     <string name="none" msgid="6633966646410296520">"Nėra"</string>
+    <string name="edge" msgid="7036064886242147551">"Kraštai"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Vorholas"</string>
+    <string name="downsample" msgid="3552938534146980104">"Mažinimas"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Raudona"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Žalia"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Mėlyna"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stilius"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Dydis"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Spalva"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linijos"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Žymeklis"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Taškymas"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Išvalyti"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Pasirinkti tinkintą spalvą"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Pasirinkti spalvą"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Pasirinkti dydį"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Gerai"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Originalus"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Rezultatas"</string>
 </resources>
diff --git a/res/values-lt/photoeditor_strings.xml b/res/values-lt/photoeditor_strings.xml
deleted file mode 100644
index bb2a645..0000000
--- a/res/values-lt/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Nepavyko įkelti nuotraukos"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Nepavyko išsaugoti redaguotos nuotraukos"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Redaguota nuotrauka išsaugota aplanke <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Atmesti neišsaugotus pakeitimus?"</string>
-    <string name="yes" msgid="5402582493291792293">"Taip"</string>
-    <string name="save" msgid="5516670392524294967">"IŠSAUGOT"</string>
-    <string name="autofix" msgid="1663414996270538748">"Automat. nustat."</string>
-    <string name="crop" msgid="7598378507763334041">"Apkarpyti"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Kryžm. apdor."</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentika"</string>
-    <string name="doodle" msgid="1686409894518940990">"Logotipų puoš."</string>
-    <string name="duotone" msgid="8145893940788467106">"Du tonai"</string>
-    <string name="facelift" msgid="6205748523156172637">"Veido spindesys"</string>
-    <string name="facetan" msgid="4412831806626044111">"Veido įdegis"</string>
-    <string name="filllight" msgid="2644989991700022526">"Užpild. šviesa"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Žuvies akis"</string>
-    <string name="flip" msgid="2357692401826287480">"Apversti"</string>
-    <string name="grain" msgid="7487585304579789098">"Juostos grūd."</string>
-    <string name="grayscale" msgid="7641563843060945228">"Nespalvota"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Nespalvotas"</string>
-    <string name="highlight" msgid="3902653944386623972">"Paryškinimai"</string>
-    <string name="lomoish" msgid="1270032154357186736">"„Lomo“ efekt."</string>
-    <string name="negative" msgid="1985508917342811252">"Negatyvas"</string>
-    <string name="posterize" msgid="4139212359561383385">"Mažinti spalv."</string>
-    <string name="redeye" msgid="4958448806369928239">"Raudonos akys"</string>
-    <string name="rotate" msgid="6607597269792373083">"Pasukti"</string>
-    <string name="saturation" msgid="8621322012271169931">"Spalvų sodr."</string>
-    <string name="sepia" msgid="7978093531824705601">"Tamsiai rusv."</string>
-    <string name="shadow" msgid="8235188588101973090">"Šešėliai"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Paryškinti"</string>
-    <string name="straighten" msgid="5217801513491493491">"Tiesinti"</string>
-    <string name="temperature" msgid="1607987938521534517">"Šiluma"</string>
-    <string name="tint" msgid="154435943863418434">"Atspalvis"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjetė"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Jei norite apkarpyti nuotr., vilkite žymeklius"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Pieškite ant nuotraukos, jei norite taikyti logotipų puošybą"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Vilkite nuotrauką, kad ją apverstumėte"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Paliesk. raud. akis, kad pašal. jų efek."</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Vilkite nuotrauką, kad ją apsuktumėte"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Vilkite nuotrauką, kad ją ištiesintumėte"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ekspozicijos efektai"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Spalvų efektai"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Meniški efektai"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Taisyti"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Anuliuoti"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Grąžinti"</string>
-</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 20cbfc3..228aa63 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Sukti į dešinę"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Nepavyko surasti elemento."</string>
     <string name="edit" msgid="1502273844748580847">"Redaguoti"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Papr. redagavimas"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Talpinimo užklausų apdorojimas"</string>
     <string name="caching_label" msgid="4521059045896269095">"Talpinama…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Apkarpyti"</string>
     <string name="trim_action" msgid="703098114452883524">"Apkarpyti"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Nutildyti"</string>
     <string name="set_as" msgid="3636764710790507868">"Nustatyti kaip"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Negalima nutildyti vaizdo įr."</string>
     <string name="video_err" msgid="7003051631792271009">"Negalima paleisti vaizdo įrašo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Pagal vietą"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Pagal laiką"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automobiliai"</string>
     <string name="flash_on" msgid="7891556231891837284">"Blykstė suveikė"</string>
     <string name="flash_off" msgid="1445443413822680010">"Be blykstės"</string>
+    <string name="unknown" msgid="3506693015896912952">"Nežinoma"</string>
     <string name="ffx_original" msgid="372686331501281474">"Originalas"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Senovinis"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Momentinis"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nepasiekiama jokia išorinė atmintinė"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmo juostos rodinys"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Tinklelio rodinys"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Viso ekrano rodinys"</string>
     <string name="trimming" msgid="9122385768369143997">"Apkarpymas"</string>
+    <string name="muting" msgid="5094925919589915324">"Nutildoma"</string>
     <string name="please_wait" msgid="7296066089146487366">"Palaukite"</string>
-    <string name="save_into" msgid="4960537214388766062">"Išsaugomas apkarpytas vaizdo įrašas albume:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Vaizdo įrašas išsaugomas albume „<xliff:g id="ALBUM_NAME">%1$s</xliff:g>“…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Negalima apkarpyti: tikslinis vaizdo įrašas per trumpas"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Atvaizduojama panorama"</string>
     <string name="save" msgid="613976532235060516">"Išsaugoti"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Nuskaitomas turinys..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Nuskaityta elementų: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"Nuskaityta elementų: %1$d"</item>
+    <item quantity="other" msgid="3138021473860555499">"Nuskaityta elementų: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Rūšiuojama..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Nuskaityta"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importuojama..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nėra turinio, kurį galima importuoti į šį įrenginį."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nėra prijungto MTP įrenginio"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Fotoaparato klaida"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nepavyksta prisijungti prie kameros."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Dėl saugumo politikos kamera neleidžiama."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotoaparatas"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Nešiojamoji vaizdo kamera ir magnetofonas"</string>
+    <string name="wait" msgid="8600187532323801552">"Palaukite..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Prieš naudodami fotoaparatą įrenkite USB atmintį."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Prieš naudodami fotoaparatą įdėkite SD kortelę."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Ruošiama USB atmintinė..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Ruošiama SD kortelė..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nepavyko pasiekti USB atminties."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nepavyko pasiekti SD kortelės."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ATŠAUKTI"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ATLIKTA"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Laiko tarpo įrašymas"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Pasirin. fotoaparatą"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Atgal"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Šriftas"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Išsaugoti vietą"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"VIETOVĖ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Atvirkštinis laikmatis"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sek."</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sek."</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Laikmačio pyps."</string>
+    <string name="setting_off" msgid="4480039384202951946">"Išjungta"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Įjungta"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Vaizdo įrašo kokybė"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Aukšta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Prasta"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Laiko intervalas"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Fotoaparato nustatymai"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Nešiojamosios vaizdo kameros ir magnetofono nustatymai"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Paveikslėlio dydis"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapiks."</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapiks."</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 mln. piks."</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapikseliai"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 mln. piks."</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 mln. piks."</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 megap. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 mln. piks."</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 mln. piks."</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokusavimo režimas"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatiškai"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Begalybė"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makrokomanda"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATINIS"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"BEGALYBĖ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKROKOMANDA"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"„Flash“ režimas"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"BLYKSTĖS REŽIMAS"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatiškai"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Įjungta"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Išjungta"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATINĖ BLYKSTĖ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLYKSTĖ ĮJUNGTA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLYKSTĖ IŠJUNGTA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Baltos spalvos balansas"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALTOS SPALVOS BALANSAS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatiškai"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Akinančiai skaistus"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dienos šviesa"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescencinis"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Debesuota"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATINIS"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"AKINANČIAI SKAISTUS"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DIENOS ŠVIESA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENCINIS"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"DEBESUOTA"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scenų režimas"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatiškai"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Veiksmas"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Naktis"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Saulėlydis"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Vakarėlis"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NĖRA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"VEIKSMAS"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NAKTIS"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SAULĖLYDIS"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"VAKARĖLIS"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ATVIRKŠTINIS LAIKMATIS"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"IŠJUNGTI LAIKMATĮ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEK."</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEK."</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEK."</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEK."</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Negalima pasirinkti, kai veikia scenos režimas."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Išlaikymas"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"IŠLAIKYMAS"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PRIEKINIS FOTOAPARATAS"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"UŽPAKALINIS FOTOAPARATAS"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Gerai"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB atmintinėje trūksta vietos. Pakeiskite kokybės nustatymą arba ištrinkite kelis vaizdus ar kitus failus."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD kortelėje trūksta vietos. Pakeiskite kokybės nustatymą arba ištrinkite kelis vaizdus ar kitus failus."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Pasiekta dydžio riba."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Per greitai"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Ruošiama panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Nepavyko išsaugoti panoramos."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Fiksuojama panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Laukiama ankstesnės panoramos"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Išsaugoma..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Atvaizduojama panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Palieskite, kad fokusuot."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efektai"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Joks"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Suspausti"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Didelės akys"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Didelė burna"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Maža burna"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Didelė nosis"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Mažos akys"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Kosmose"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Saulėlydis"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Vaizdo įr."</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Išjunkite įrenginį."\n"Trumpam išeikite iš rodinio."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Palieskite, kad įrašydami nufotografuotumėte."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Prasidėjo vaizdo įrašo įrašymas."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Vaizdo įrašo įrašymas sustabdytas."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Moment. vaizdo įrašo vaizdas neleidž., kai įjungti spec. efektai."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Išvalyti efektus"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"JUOKINGI VEIDAI"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FONAS"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Užrakto mygtukas"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Meniu mygtukas"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Naujausia nuotrauka"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Priekinis ir galinis fotoaparato jungikliai"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kameros, vaizdo įrašo ar panoramos parinkiklis"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Daugiau nustatymų valdiklių"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Uždaryti nustatymų valdiklius"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Mastelio keitimo valdymas"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Sumažinti %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Padidinti %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s žymimasis laukelis"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Perjungti į fotografavimo režimą"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Perjungti į vaizdo įrašo režimą"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Perjungti į panoramos režimą"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Perjungti į naują panoramą"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Atšaukimas peržiūros režimu"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Atlikta peržiūros režimu"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Peržiūrėti / fotografuoti arba filmuoti iš naujo"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Leisti vaizdo įrašą"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pristabdyti vaizdo įrašą"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Įkelti vaizdo įrašą iš naujo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Vaizdo įrašų grotuvo laiko juosta"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ĮJUNGTA"</string>
+    <string name="capital_off" msgid="7231052688467970897">"IŠJUNGTA"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Išj."</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sek."</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 min."</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 val."</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 val."</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekundės"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutės"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"valandos"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Atlikta"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Nustatyti laiko intervalą"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Laiko intervalo funkcija išjungta. Įjunkite ją, kad nustatytumėte laiko intervalą."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Atvirkštinis laikmatis išjungtas. Įjunkite jį prieš fotografuodami."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Nustatyti trukmę sekundėmis"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Skaičiuojamas laikas iki fotografavimo"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Atsiminti nuotraukų vietas?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Žymėkite nuotraukas ir vaizdo įrašus nurodydami vietas, kur jie buvo sukurti."\n\n"Kitos programos gali pasiekti šią informaciją kartu su išsaugotais vaizdais."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ne, ačiū"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Taip"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotoaparatas"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Paieška"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Nuotraukos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumai"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"DAUGIAU PARINKČIŲ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"NUSTATYMAI"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"Nuotraukų: %1$d"</item>
+    <item quantity="other" msgid="3813306834113858135">"Nuotraukų: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-lv/filtershow_strings.xml b/res/values-lv/filtershow_strings.xml
index af201d7..a8593d2 100644
--- a/res/values-lv/filtershow_strings.xml
+++ b/res/values-lv/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nevar ielādēt attēlu."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Notiek fona tapetes iestatīšana"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Nevarēja lejupielādēt fotoattēlu. Tīkls nav pieejams."</string>
     <string name="original" msgid="3524493791230430897">"Oriģināls"</string>
     <string name="borders" msgid="2067345080568684614">"Robežas"</string>
-    <string name="done" msgid="3112344807927554662">"Gatavs"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Atsaukt"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Atcelt atsaukšanu"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Rādīt vēsturi"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Slēpt vēsturi"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Rādīt attēla statusu"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Slēpt attēla statusu"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Rādīt lietotos efektus"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Slēpt lietotos efektus"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Iestatījumi"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Šajā attēlā ir veiktas izmaiņas, kas nav saglabātas."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vai vēlaties saglabāt pirms iziešanas?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Saglabāt un iziet"</string>
+    <string name="exit" msgid="242642957038770113">"Iziet"</string>
     <string name="history" msgid="455767361472692409">"Vēsture"</string>
     <string name="reset" msgid="9013181350779592937">"Atiestatīt"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Pašreizējais attēla statuss"</string>
+    <string name="imageState" msgid="8632586742752891968">"Izmantotie efekti"</string>
     <string name="compare_original" msgid="8140838959007796977">"Salīdzināt"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Lietot"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Atiestatīt"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nav"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fiksēts"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Ekspozīcija"</string>
     <string name="sharpness" msgid="6463103068318055412">"Asums"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Aut. krāsu pal."</string>
     <string name="hue" msgid="6231252147971086030">"Nokrāsa"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Ēnas"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Gaišās vietas"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Līknes"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjete"</string>
     <string name="redeye" msgid="4508883127049472069">"Sarkano acu ef."</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Zīmējumi"</string>
     <string name="straighten" msgid="26025591664983528">"Iztaisnošana"</string>
     <string name="crop" msgid="5781263790107850771">"Izgriešana"</string>
     <string name="rotate" msgid="2796802553793795371">"Pagriezt"</string>
     <string name="mirror" msgid="5482518108154883096">"Spogulis"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatīvs"</string>
     <string name="none" msgid="6633966646410296520">"Nav"</string>
+    <string name="edge" msgid="7036064886242147551">"Malas"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Vorhols"</string>
+    <string name="downsample" msgid="3552938534146980104">"Sam. liel."</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Sarkans"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zaļš"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Zils"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stils"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Izmēri"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Krāsa"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Līnijas"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marķieris"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Šļakatas"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Notīrīt"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Izvēlēties pielāgotu krāsu"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Krāsas atlase"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Izmēru atlase"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Labi"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Sākotnējais"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Rezultāts"</string>
 </resources>
diff --git a/res/values-lv/photoeditor_strings.xml b/res/values-lv/photoeditor_strings.xml
deleted file mode 100644
index dfffd2e..0000000
--- a/res/values-lv/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotostudija"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotoattēlu nevarēja ielādēt."</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Rediģēto fotoattēlu nevarēja saglabāt."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Rediģētais attēls ir saglabāts mapē <xliff:g id="FOLDER_NAME">%s</xliff:g>."</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vai atmest nesaglabātās izmaiņas?"</string>
-    <string name="yes" msgid="5402582493291792293">"Jā"</string>
-    <string name="save" msgid="5516670392524294967">"SAGLABĀT"</string>
-    <string name="autofix" msgid="1663414996270538748">"Automātiska labošana"</string>
-    <string name="crop" msgid="7598378507763334041">"Apgriešana"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Kļūd. apstrāde"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentārs"</string>
-    <string name="doodle" msgid="1686409894518940990">"Kricelējums"</string>
-    <string name="duotone" msgid="8145893940788467106">"Divtoņu"</string>
-    <string name="facelift" msgid="6205748523156172637">"Sejas mirdzums"</string>
-    <string name="facetan" msgid="4412831806626044111">"Sejas iedegums"</string>
-    <string name="filllight" msgid="2644989991700022526">"Izm. pretgaismu"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Platleņķis"</string>
-    <string name="flip" msgid="2357692401826287480">"Apvērst"</string>
-    <string name="grain" msgid="7487585304579789098">"Graudaina filma"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Melnbalts"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Melnbalts"</string>
-    <string name="highlight" msgid="3902653944386623972">"Gaismas efekti"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomogrāfija"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatīvs"</string>
-    <string name="posterize" msgid="4139212359561383385">"Veidot plakātu"</string>
-    <string name="redeye" msgid="4958448806369928239">"Sarkanās acis"</string>
-    <string name="rotate" msgid="6607597269792373083">"Pagriezt"</string>
-    <string name="saturation" msgid="8621322012271169931">"Piesātinājums"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sēpija"</string>
-    <string name="shadow" msgid="8235188588101973090">"Ēnas"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Padarīt asāku"</string>
-    <string name="straighten" msgid="5217801513491493491">"Iztaisnot"</string>
-    <string name="temperature" msgid="1607987938521534517">"Siltums"</string>
-    <string name="tint" msgid="154435943863418434">"Nianse"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjete"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Velciet marķierus, lai apgrieztu fotoattēlu."</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Velciet uz fotoattēla, lai kricelētu."</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Velciet fotoattēlu, lai to apgrieztu."</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Piesk. sark. acīm, lai noņ. šo efektu."</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Velciet fotoattēlu, lai to pagrieztu."</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Velciet fotoattēlu, lai to iztaisnotu."</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ekspozīcijas efekti"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Krāsu efekti"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Mākslinieciski efekti"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Uzlabošana"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Atsaukt"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Atcelt atsaukšanu"</string>
-</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index f23e066..25a9194 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Pagriezt pa labi"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Nevarēja atrast vienumu."</string>
     <string name="edit" msgid="1502273844748580847">"Rediģēt"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Parauga rediģēšana"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Tiek apstrādāti pieprasījumi rakstīšanai kešatmiņā"</string>
     <string name="caching_label" msgid="4521059045896269095">"Glabā kešatm..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Apgriezt"</string>
     <string name="trim_action" msgid="703098114452883524">"Apgriezt"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Izslēgt skaņu"</string>
     <string name="set_as" msgid="3636764710790507868">"Iestatīt kā:"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Nevar izslēgt videoklipa skaņu"</string>
     <string name="video_err" msgid="7003051631792271009">"Nevar atskaņot video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Pēc atrašanās vietas"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Pēc uzņemšanas laika"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Automātiski"</string>
     <string name="flash_on" msgid="7891556231891837284">"Zibspuldze ir aktivizēta"</string>
     <string name="flash_off" msgid="1445443413822680010">"Bez zibspuldzes"</string>
+    <string name="unknown" msgid="3506693015896912952">"Nezināms"</string>
     <string name="ffx_original" msgid="372686331501281474">"Sākotnējais"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Senlaicīgs"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Ātrais foto"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nav ārējās atmiņas"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Kinolentes skats"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Režģa skats"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Pilnekrāna skatījums"</string>
     <string name="trimming" msgid="9122385768369143997">"Notiek apgriešana"</string>
+    <string name="muting" msgid="5094925919589915324">"Skaņas izslēgšana"</string>
     <string name="please_wait" msgid="7296066089146487366">"Lūdzu, uzgaidiet!"</string>
-    <string name="save_into" msgid="4960537214388766062">"Apgrieztais videoklips tiek saglabāts šajā albumā:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Notiek videoklipa saglabāšana albumā <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nevar apgriezt: mērķa videoklips ir pārāk īss."</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Notiek panorāmas atveidošana"</string>
     <string name="save" msgid="613976532235060516">"Saglabāt"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Notiek satura skenēšana..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Skenēti %1$d vienumi"</item>
+    <item quantity="one" msgid="4340019444460561648">"Skenēts %1$d vienums"</item>
+    <item quantity="other" msgid="3138021473860555499">"Skenēti %1$d vienumi"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Notiek kārtošana..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skenēšana ir pabeigta."</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Notiek importēšana..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nav pieejams šajā ierīcē importējams saturs."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nav pievienota neviena MTP ierīce."</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kameras kļūda"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nevar izveidot savienojumu ar kameru."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera ir atspējota drošības politiku dēļ."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Lūdzu, uzgaidiet…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Pirms kameras lietošanas pievienojiet USB atmiņu."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Pirms kameras izmantošanas ievietojiet SD karti."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Notiek USB atm. sagatavošana"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Notiek SD kartes sagatavošana..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nevarēja piekļūt USB atmiņai."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nevarēja piekļūt SD kartei."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ATCELT"</string>
+    <string name="review_ok" msgid="1156261588693116433">"GATAVS"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Ierakstīšana laika intervāla režīmā"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Kameras izvēlēšanās"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Atpakaļ"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Priekšpuse"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Saglabāt atrašanās vietu"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"ATRAŠANĀS VIETA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Laika atskaites taimeris"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekunde"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sekundes"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Atskaņot signālu"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Izslēgts"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Ieslēgts"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Video kvalitāte"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Augsta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Zema"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Laika intervāls"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kameras iestatījumi"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokameras iestatījumi"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Attēla izmērs"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 megap. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapikseļi"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapikselis"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokusa režīms"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automātiski"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Bezgalība"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMĀTISKI"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"BEZGALĪBA"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Zibspuldzes režīms"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"ZIBSPULDZES REŽĪMS"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automātiski"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Ieslēgts"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Izslēgt"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMĀTISKĀ ZIBSPULDZE"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"IESLĒGTA ZIBSPULDZE"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"IZSLĒGTA ZIBSPULDZE"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Baltās krāsas balanss"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALTĀ BALANSS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automātiski"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Spožs"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dienasgaisma"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescējošs"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Mākoņains"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMĀTISKI"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"KVĒLSPULDZE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DIENASGAISMA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCĒJOŠS"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"MĀKOŅAINS"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Ainas režīms"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automātiski"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Kustība"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nakts"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Saulriets"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Viesības"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NAV"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"SPRAIGA DARBĪBA"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NAKTS"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SAULRIETS"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"BALLĪTE"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"LAIKA ATSKAITES TAIMERIS"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TAIMERIS IR IZSLĒGTS"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDE"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDES"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDES"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDES"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Nevar atlasīt ainavas režīmā."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Ekspozīcija"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EKSPOZĪCIJA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"AUGSTS DINAMISKAIS DIAPAZONS"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PRIEKŠĒJĀ KAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"AIZMUGURĒJĀ KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Labi"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB atmiņa drīz būs pilna. Mainiet kvalitātes iestatījumu vai dzēsiet dažus attēlus vai citus failus."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD karte drīz būs pilna. Mainiet kvalitātes iestatījumu vai dzēsiet dažus attēlus vai citus failus."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Lieluma ierobežojums ir sasniegts."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Pārāk ātri"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Notiek panorāmas sagatavošana"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Nevarēja saglabāt panorāmu."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorāma"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Notiek panorāmas tveršana"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Tiek gaidīta iepriekšējā panorāma."</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Saglabā..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Notiek panorāmas atveide"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Pieskarieties, lai fokusētu."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efekti"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Nav"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Saspiest"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Lielas acis"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Liela mute"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Maza mute"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Liels deguns"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Mazas acis"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Kosmosā"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Saulriets"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Videoklips"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Novietojiet ierīci uz leju."\n"Uz brīdi izejiet no kameras skata."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Pieskarieties, lai uzņemtu fotoattēlu ieraksta laikā."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video ierakstīšana ir sākta."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video ierakstīšana ir pārtraukta."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Video momentuzņēmums ir atspējots, ja ir ieslēgti specefekti."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Noņemt efektus"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"SMIEKL. SEJAS IZTEIKSMES"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FONS"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Slēdža poga"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Izvēlnes poga"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Pēdējais fotoattēls"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Priekšējais un aizmugurējais kameras slēdzis"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kameras, videokameras vai panorāmas atlasītājs"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Vairāk iestatījumu vadīklu"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Aizvērt iestatījumu vadīklas"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Tālummaiņas vadība"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Samazināt %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Palielināt %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s izvēles rūtiņa"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Pārslēgt uz fotoattēlu režīmu"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Pārslēgt uz video režīmu"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Pārslēgt uz panorāmas režīmu"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Pārslēgties uz jaunu panorāmu"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Atcelt pārskatīšanu"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Pabeigt pārskatīšanu"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Pārskatīt atkārtotu attēlu"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Atskaņot videoklipu"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pārtraukt videoklipa atskaņošanu"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Atkārtoti ielādēt videoklipu"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Videoklipu atskaņotāja laika josla"</string>
+    <string name="capital_on" msgid="5491353494964003567">"IESLĒGTS"</string>
+    <string name="capital_off" msgid="7231052688467970897">"IZSLĒGTS"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Beidzies"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekundes"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minūte"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minūtes"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 stunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 stundas"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 stundas"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunde(-es)"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minūte(-es)"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"stundas"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Gatavs"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Laika intervāla iestatīšana"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Palēninātās filmēšanas funkcija ir izslēgta. Ieslēdziet to, lai iestatītu laika intervālu."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Laika atskaites taimeris ir izslēgts. Ieslēdziet to, lai skaitītu laiku pirms attēla uzņemšanas."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Iestatīt ilgumu sekundēs"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Notiek laika skaitīšana līdz fotoattēla uzņemšanai"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Vai atcerēties fotoattēlu uzņemšanas vietas?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Atzīmē jūsu fotoattēlos un videoklipos atrašanās vietas, kurās tie tika uzņemti."\n\n"Citas lietotnes var piekļūt šai informācijai līdz ar saglabātajiem attēliem."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nē, paldies!"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Jā"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Meklēt"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotoattēli"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"CITAS OPCIJAS"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"IESTATĪJUMI"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotoattēls"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotoattēli"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ms/filtershow_strings.xml b/res/values-ms/filtershow_strings.xml
index 81fa7ad..cac3e7d 100644
--- a/res/values-ms/filtershow_strings.xml
+++ b/res/values-ms/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Tidak dapat memuatkan imej!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Menetapkan kertas dinding"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Tidak dapat memuat turun foto. Rangkaian tidak tersedia."</string>
     <string name="original" msgid="3524493791230430897">"Asli"</string>
     <string name="borders" msgid="2067345080568684614">"Sempadan"</string>
-    <string name="done" msgid="3112344807927554662">"Selesai"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Buat asal"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Buat semula"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Tunjukkan Sejarah"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Sembunyikan Sejarah"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Tunjukkan Keadaan Imej"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Sembunyikan Keadaan Imej"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Tunjukkan Kesan Digunakan"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Sembunyikan Kesan Digunakan"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Tetapan"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Terdapat perubahan kepada imej ini yang tidak disimpan."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Adakah anda ingin simpan sebelum keluar?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Simpan dan Keluar"</string>
+    <string name="exit" msgid="242642957038770113">"Keluar"</string>
     <string name="history" msgid="455767361472692409">"Sejarah"</string>
     <string name="reset" msgid="9013181350779592937">"Tetapkan semula"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Keadaan Imej Semasa"</string>
+    <string name="imageState" msgid="8632586742752891968">"Kesan Digunakan"</string>
     <string name="compare_original" msgid="8140838959007796977">"Bandingkan"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Gunakan"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Tetapkan semula"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Tiada"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Dibetulkan"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planet Kecil"</string>
     <string name="exposure" msgid="6526397045949374905">"Dedahan"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ketajaman"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autowarna"</string>
     <string name="hue" msgid="6231252147971086030">"Rona"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bayang-bayang"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Serlahan"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Lengkung"</string>
     <string name="vignette" msgid="934721068851885390">"Vignet"</string>
     <string name="redeye" msgid="4508883127049472069">"Mata Merah"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Lukis"</string>
     <string name="straighten" msgid="26025591664983528">"Luruskan"</string>
     <string name="crop" msgid="5781263790107850771">"Pangkas"</string>
     <string name="rotate" msgid="2796802553793795371">"Putar"</string>
     <string name="mirror" msgid="5482518108154883096">"Cermin"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatif"</string>
     <string name="none" msgid="6633966646410296520">"Tiada"</string>
+    <string name="edge" msgid="7036064886242147551">"Pinggir"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Mengecil"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Merah"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Hijau"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Biru"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Gaya"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Saiz"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Warna"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Garisan"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Penanda"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Percik"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Padam bersih"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Pilih warna peribadi"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Pilih Warna"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Pilih Saiz"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Asal"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Hasil"</string>
 </resources>
diff --git a/res/values-ms/photoeditor_strings.xml b/res/values-ms/photoeditor_strings.xml
deleted file mode 100644
index 3ba175f..0000000
--- a/res/values-ms/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Studio Foto"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Tidak dapat memuatkan foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Tidak dapat menyimpan foto yang diedit"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Foto diedit disimpan ke <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Buang perubahan yang belum disimpan?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ya"</string>
-    <string name="save" msgid="5516670392524294967">"SIMPAN"</string>
-    <string name="autofix" msgid="1663414996270538748">"Auto-baiki"</string>
-    <string name="crop" msgid="7598378507763334041">"Pangkas"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proses silang"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentari"</string>
-    <string name="doodle" msgid="1686409894518940990">"Coretan"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dua tona"</string>
-    <string name="facelift" msgid="6205748523156172637">"Muka Berseri-seri"</string>
-    <string name="facetan" msgid="4412831806626044111">"Muka Sawo Matang"</string>
-    <string name="filllight" msgid="2644989991700022526">"Cahaya Pemenuh"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Mata ikan"</string>
-    <string name="flip" msgid="2357692401826287480">"Balikkan"</string>
-    <string name="grain" msgid="7487585304579789098">"Ira Filem"</string>
-    <string name="grayscale" msgid="7641563843060945228">"H &amp; P"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Hitam putih"</string>
-    <string name="highlight" msgid="3902653944386623972">"Serlahan"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
-    <string name="posterize" msgid="4139212359561383385">"Pemposteran"</string>
-    <string name="redeye" msgid="4958448806369928239">"Mata Merah"</string>
-    <string name="rotate" msgid="6607597269792373083">"Putar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Ketepuan"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Bayang-bayang"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Tajamkan"</string>
-    <string name="straighten" msgid="5217801513491493491">"Luruskan"</string>
-    <string name="temperature" msgid="1607987938521534517">"Cerah"</string>
-    <string name="tint" msgid="154435943863418434">"Seri warna"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignet"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Seret penanda untuk memangkas foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Lukis pada foto untuk mencoret"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Seret foto untuk menterbalikkannya"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Ketik pada mata merah untuk mengalih keluar"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Seret foto untuk memutarnya"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Seret foto untuk meluruskannya"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Kesan dedahan"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Kesan warna"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Kesan artistik"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Baiki"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Buat asal"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Buat semula"</string>
-</resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 729f2d4..e27eed3 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Putar ke kanan"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Tidak dapat mencari item."</string>
     <string name="edit" msgid="1502273844748580847">"Edit"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edit Ringkas"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Memproses permintaan cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Mengcache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Pangkas"</string>
     <string name="trim_action" msgid="703098114452883524">"Pangkas"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Redam"</string>
     <string name="set_as" msgid="3636764710790507868">"Tetapkan sebagai"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Tidak dapat meredam video."</string>
     <string name="video_err" msgid="7003051631792271009">"Tidak boleh memainkan video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Mengikut lokasi"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Mengikut masa"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Denyar dilepas"</string>
     <string name="flash_off" msgid="1445443413822680010">"Tiada denyar"</string>
+    <string name="unknown" msgid="3506693015896912952">"Tak diketahui"</string>
     <string name="ffx_original" msgid="372686331501281474">"Asli"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintaj"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Semerta"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Tiada storan luar tersedia"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Pandangan jalur filem"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Paparan grid"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Pandangan skrin penuh"</string>
     <string name="trimming" msgid="9122385768369143997">"Mencantas"</string>
+    <string name="muting" msgid="5094925919589915324">"Meredam"</string>
     <string name="please_wait" msgid="7296066089146487366">"Sila tunggu"</string>
-    <string name="save_into" msgid="4960537214388766062">"Menyimpan video yang dipotong ke dalam album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Menyimpan video ke <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Tidak boleh mencantas: video sasaran terlalu pendek"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Menghasilkan panorama"</string>
     <string name="save" msgid="613976532235060516">"Simpan"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Mengimbas kandungan..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d item diimbas"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item diimbas"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d item diimbas"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Mengisih..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Pengimbasan selesai"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Mengimport..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Tiada kandungan tersedia untuk diimport pada peranti ini."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Tiada peranti MTP disambungkan"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Ralat kamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Tidak boleh menyambung kepada kamera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera telah dilumpuhkan kerana dasar keselamatan."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Kamkorder"</string>
+    <string name="wait" msgid="8600187532323801552">"Sila tunggu..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Lekapkan storan USB sebelum menggunakan kamera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Masukkan kad SD sebelum menggunakan kamera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Menyediakan storan USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Menyediakan kad SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Tidak dapat mengakses storan USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Tidak dapat mengakses kad SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"BATAL"</string>
+    <string name="review_ok" msgid="1156261588693116433">"SELESAI"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Rakaman selang masa"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Pilih kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Belakang"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Depan"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Lokasi stor"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKASI"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Pemasa hitung detik"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 saat"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d saat"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Bip utk htg dtk"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Matikan"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Hidupkan"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kualiti video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Tinggi"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Rendah"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Selang masa"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Tetapan kamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Tetapan kamkorder"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Saiz gambar"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M piksel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"Piksel 8M"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M piksel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M piksel"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M piksel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M piksel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M piksel (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M piksel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M piksel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Mod fokus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infiniti"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITI"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Mod denyar"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MOD DENYAR"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Auto"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Hidup"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Mati"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTO DENYAR"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"HIDUPKAN DENYAR"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"MATIKAN DENYAR"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Imbangan putih"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"IMBANGAN PUTIH"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Auto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Berpijar"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Siang hari"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Pendarfluor"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Mendung"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"PIJAR"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"SIANG HARI"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"PENDARFLUOR"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"MENDUNG"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Mod pemandangan"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Auto"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Aksi"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Malam"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Matahari Terbenam"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Parti"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"TIADA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"TINDAKAN"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"MALAM"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"MATAHARI TERBENAM"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PARTI"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"PEMASA HITUNG DETIK"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"PEMASA DIMATIKAN"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SAAT"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SAAT"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SAAT"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SAAT"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Tidak boleh dipilih dalam mod pemandangan."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Dedahan"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"DEDAHAN"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"KAMERA DEPAN"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"KAMERA BELAKANG"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Storan USB anda kehabisan ruang. Tukar tetapan mutu atau padamkan beberapa imej atau fail lain."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Kad SD anda kehabisan ruang. Tukar tetapan mutu atau padamkan beberapa imej atau fail lain."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Had saiz dicapai."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Trlalu cepat"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Menyediakan panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Tidak dapat menyimpan panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Menangkap gambar panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Menunggu panorama sebelumnya"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Menyimpan…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Menghasilkan panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Sentuh untuk memfokus."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Kesan"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Tiada"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Picit"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Mata besar"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Mulut besar"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Mulut kecil"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Hidung besar"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Mata kecil"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Di angkasa"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Matahari Terbenam"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Video anda"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Tetapkan peranti anda ke bawah."\n"Keluar dari pandangan seketika."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Sentuh untuk mengambil gambar semasa merakam."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Rakaman video telah bermula."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Rakaman video telah berhenti."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Petikan video dilumpuhkan apabila kesan khas dihidupkan."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Padamkan kesan"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"MUKA BODOH"</string>
+    <string name="effect_background" msgid="6579360207378171022">"LATAR BELAKANG"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Butang pengatup"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Butang menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto paling terbaru"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Suis kamera depan dan belakang"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Pemilih kamera, video atau panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Lagi kawalan tetapan"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Tutup kawalan tetapan"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Kawalan zum"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Pengurangan %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Tingkatkan %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s kotak semak"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Tukar ke foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Tukar kepada video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Tukar kepada panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Beralih ke panorama baharu"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Semakan dibatalkan"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Semakan selesai"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Semak penggambaran semula"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Mainkan video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Jeda video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Muat semula video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Bar masa pemain video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"HIDUPKAN"</string>
+    <string name="capital_off" msgid="7231052688467970897">"MATIKAN"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Dimatikan"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minit"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 jam"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 jam"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"saat"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minit"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"jam"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Selesai"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Tetapkan Selang Masa"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Ciri selang masa dimatikan. Hidupkannya untuk menetapkan selang masa."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Pemasa hitung detik dimatikan. Hidupkannya untuk menghitung detik sebelum mengambil gambar."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Tetapkan tempoh dalam saat"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Menghitung detik untuk mengambil gambar"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Ingat lokasi foto?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Teg foto dan video anda dengan lokasi tempat diambil."\n\n"Apl lain boleh mengakses maklumat ini bersama dengan imej disimpan anda."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Tidak, terima kasih"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ya"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Cari"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foto"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"LAGI PILIHAN"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"TETAPAN"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foto"</item>
+  </plurals>
 </resources>
diff --git a/res/values-nb/filtershow_strings.xml b/res/values-nb/filtershow_strings.xml
index faa77ce..2fe1324 100644
--- a/res/values-nb/filtershow_strings.xml
+++ b/res/values-nb/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Kan ikke laste inn bildet."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Angir bakgrunn …"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Kunne ikke laste ned bildet. Nettverket er utilgjengelig."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Kantlinjer"</string>
-    <string name="done" msgid="3112344807927554662">"Fullført"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Angre"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Gjør om"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Vis loggen"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Skjul loggen"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Vis bildetilstanden"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Skjul bildetilstanden"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Vis brukte effekter"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Skjul brukte effekter"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Innstillinger"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Det er ulagrede endringer i dette bildet."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vil du lagre før du avslutter?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Lagre og avslutt"</string>
+    <string name="exit" msgid="242642957038770113">"Avslutt"</string>
     <string name="history" msgid="455767361472692409">"Logg"</string>
     <string name="reset" msgid="9013181350779592937">"Tilbakestill"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Nåværende bildetilstand"</string>
+    <string name="imageState" msgid="8632586742752891968">"Brukte effekter"</string>
     <string name="compare_original" msgid="8140838959007796977">"Sammenlign"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Bruk"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Tilbakestill"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ingen"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Låst"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Liten planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Eksponering"</string>
     <string name="sharpness" msgid="6463103068318055412">"Skarphet"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autofarger"</string>
     <string name="hue" msgid="6231252147971086030">"Nyanse"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skygger"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Lyse punkter"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurver"</string>
     <string name="vignette" msgid="934721068851885390">"Vignettering"</string>
     <string name="redeye" msgid="4508883127049472069">"Røde øyne"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Tegn"</string>
     <string name="straighten" msgid="26025591664983528">"Rett opp"</string>
     <string name="crop" msgid="5781263790107850771">"Beskjær"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotér"</string>
     <string name="mirror" msgid="5482518108154883096">"Speil"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Ingen"</string>
+    <string name="edge" msgid="7036064886242147551">"Rammer"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Forminske"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rød"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Grønn"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blå"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Størrelse"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Farge"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linjer"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Merkepenn"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Drypp"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Fjern"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Velg egendefinert farge"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Velg farge"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Velg størrelse"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultat"</string>
 </resources>
diff --git a/res/values-nb/photoeditor_strings.xml b/res/values-nb/photoeditor_strings.xml
deleted file mode 100644
index 7faa820..0000000
--- a/res/values-nb/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Kunne ikke laste inn bildet"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Kunne ikke lagre det redigerte bildet"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Det redigerte bildet ble lagret i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vil du forkaste endringer som ikke er lagret?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"LAGRE"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autokorrektur"</string>
-    <string name="crop" msgid="7598378507763334041">"Beskjær"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Kryssfremkall."</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentar"</string>
-    <string name="doodle" msgid="1686409894518940990">"Drodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Tofargebilde"</string>
-    <string name="facelift" msgid="6205748523156172637">"Ansiktsglød"</string>
-    <string name="facetan" msgid="4412831806626044111">"Solbruning"</string>
-    <string name="filllight" msgid="2644989991700022526">"Utfyllingslys"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fiskeøye"</string>
-    <string name="flip" msgid="2357692401826287480">"Vend"</string>
-    <string name="grain" msgid="7487585304579789098">"Kornete oppl."</string>
-    <string name="grayscale" msgid="7641563843060945228">"Svart/hvitt"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Svart/hvitt"</string>
-    <string name="highlight" msgid="3902653944386623972">"Gjør lysere"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativt"</string>
-    <string name="posterize" msgid="4139212359561383385">"Plakateffekt"</string>
-    <string name="redeye" msgid="4958448806369928239">"Røde øyne"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotering"</string>
-    <string name="saturation" msgid="8621322012271169931">"Metning"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Skygger"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Gjør skarpere"</string>
-    <string name="straighten" msgid="5217801513491493491">"Rett opp"</string>
-    <string name="temperature" msgid="1607987938521534517">"Varme"</string>
-    <string name="tint" msgid="154435943863418434">"Fargetone"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignettering"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Dra markører for å beskjære bildet"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Tegn på bildet for å drodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Dra bildet for å snu det"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Trykk på røde øyne for å fjerne dem"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Dra bildet for å rotere det"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Dra bildet for å rette på det"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Eksponeringseffekter"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Fargeeffekter"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Kustneriske effekter"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Forbedre"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Angre"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Gjør om"</string>
-</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index d2462b2..d502497 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Roter mot høyre"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Finner ikke elementet."</string>
     <string name="edit" msgid="1502273844748580847">"Rediger"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Enkel redigering"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Behandler forespørsler om bufring"</string>
     <string name="caching_label" msgid="4521059045896269095">"Henter …"</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskjær"</string>
     <string name="trim_action" msgid="703098114452883524">"Beskjær"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Kutt lyden"</string>
     <string name="set_as" msgid="3636764710790507868">"Angi som"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Videoens lyd kan ikke kuttes."</string>
     <string name="video_err" msgid="7003051631792271009">"Kan ikke spille av video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Etter posisjon"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Etter dato"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Blits brukes"</string>
     <string name="flash_off" msgid="1445443413822680010">"Uten blits"</string>
+    <string name="unknown" msgid="3506693015896912952">"Ukjent"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Polaroid"</string>
@@ -193,10 +197,241 @@
     <string name="no_external_storage" msgid="95726173164068417">"Ekstern lagringsplass ikke tilgjengelig"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstripevisning"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Rutenettvisning"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Fullskjermvisning"</string>
     <string name="trimming" msgid="9122385768369143997">"Klipping"</string>
+    <string name="muting" msgid="5094925919589915324">"Lyden er kuttet"</string>
     <string name="please_wait" msgid="7296066089146487366">"Et øyeblikk"</string>
-    <string name="save_into" msgid="4960537214388766062">"Lagrer den klippede videoen i albumet:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Lagrer videoen i <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Videoen kan ikke klippes – målvideoen er for kort"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panoramaet settes sammen"</string>
     <string name="save" msgid="613976532235060516">"Lagre"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skanner innhold ..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elementer ble skannet"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d element ble skannet"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d elementer ble skannet"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorterer ..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skanning fullført"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importerer ..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Det er ikke noe innhold for import på denne enheten."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Ingen MTP-enhet tilkoblet"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerafeil"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Kan ikke koble til kameraet."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kameraet har blitt deaktivert på grunn av retningslinjer for sikkerhet."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Video"</string>
+    <string name="wait" msgid="8600187532323801552">"Vent litt…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Sett inn en USB-lagring før du bruker kameraet."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Sett inn et SD-kort før du bruker kameraet."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Forbereder USB-lagring …"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Forbereder minnekort…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Får ikke tilgang til USB-lagring."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Får ikke tilgang til SD-kort."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"Avbryt"</string>
+    <string name="review_ok" msgid="1156261588693116433">"FERDIG"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Tidsforkortelsesopptak"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Velg kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Bakside"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Forside"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Lagre sted i bilder"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"POSISJON"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Nedtellingstidtaker"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for pref_camera_timer_entry:other (6455381617076792481) -->
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Lydsignaler under nedtellingen"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Av"</string>
+    <string name="setting_on" msgid="8602246224465348901">"På"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videokvalitet"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Høy"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Lav"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervallmodus (time lapse)"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kamerainnstillinger"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videoinnstillinger"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Bildestørrelse"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 M piksler"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapiksler"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapiksler"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M piksler"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapiksler"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapiksler"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M piksler (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapiksler"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapiksel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokusmodus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Autofokus"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Uendelig"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"UENDELIG"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Blitzmodus"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"BLITSMODUS"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatisk"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"På"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Av"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATISK BLITS"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLITS PÅ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLITS AV"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Hvitbalanse"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"HVITBALANSE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatisk"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Flamme"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dagslys"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Lysstoffrør"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Overskyet"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"HVITGLØDENDE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAGSLYS"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCERENDE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"OVERSKYET"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Velg scenemodus"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatisk"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Action"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Natt"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Solnedgang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fest"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"INGEN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"HANDLING"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NATT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SOLNEDGANG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FEST"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"NEDTELLINGSTIDTAKER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"NEDTELLINGSTIDTAKEREN ER AV"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUND"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDER"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Kan ikke velges i scenemodus."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Eksponering"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EKSPONERING"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FRONTKAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"BAKRE KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB-lagringsplass er snart oppbrukt. Endre kvalitetsinnstillingene, eller slett bilder eller andre filer."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Lagringsplassen på SD-kortet er snart oppbrukt. Endre kvalitetsinnstillingene, eller slett bilder eller andre filer."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Videoen ble for stor."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"For rask"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Forbereder panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Kunne ikke lagre panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panoramaopptak"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Venter på forrige panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Lagrer …"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panoramaet settes sammen"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Trykk for å fokusere."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effekter"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ingen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Klem sammen"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Store øyne"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Stor munn"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Liten munn"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Stor nese"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Små øyne"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Verdensrommet"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Solnedgang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Din video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Sett enheten ned."\n"Gå ut av syne et øyeblikk."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Trykk for å ta et bilde mens du filmer."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Videoopptaket har startet."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Videoopptaket har stoppet."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Øyeblikksbilder er deaktivert når spesialeffekter er aktivert."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Fjern effekter"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"MORSOMME ANSIKTER"</string>
+    <string name="effect_background" msgid="6579360207378171022">"BAKGRUNN"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Lukkerknapp"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Meny-knapp"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Nyeste bilde"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Bryter for foran/bak"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Valg av kamera, video eller panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Flere innstillingskontroller"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Lukk innstillingskontrollene"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoomkontroll"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Reduser %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Øk %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s-avmerkingsboks"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Bytt til foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Bytt til video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Bytt til panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Bytt til ny panoramamodus"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Avbryt redigering"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Redigering fullført"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Ta nytt bilde/video"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Spill av videoen"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Sett videoen på pause"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Last inn videoen på nytt"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Tidsrad for videoavspilleren"</string>
+    <string name="capital_on" msgid="5491353494964003567">"PÅ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"AV"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Av"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minutt"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 time"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 timer"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 timer"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunder"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutter"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"timer"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Ferdig"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Fast tidsintervall"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funksjonen for intervallmodus er avslått. Slå den på for å angi et tidsintervall."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Nedtellingstidtaker er slått av. Slå den på for å telle ned før du tar et bilde."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Still varighet i sekunder"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Teller ned før bildet tas"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Vil du huske bildestedene?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Merk bildene og videoene med hvor de ble tatt."\n\n"Andre apper kan bruke denne informasjonen med de lagrede bildene dine."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nei takk"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Søk"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Bilder"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumer"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"FLERE ALTERNATIVER"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"INNSTILLINGER"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d bilde"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d bilder"</item>
+  </plurals>
 </resources>
diff --git a/res/values-nl/filtershow_strings.xml b/res/values-nl/filtershow_strings.xml
index f66e1a9..89cfa6b8 100644
--- a/res/values-nl/filtershow_strings.xml
+++ b/res/values-nl/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Kan de afbeelding niet laden."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Achtergrond instellen"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Kan de foto niet downloaden. Het netwerk is niet beschikbaar."</string>
     <string name="original" msgid="3524493791230430897">"Origineel"</string>
     <string name="borders" msgid="2067345080568684614">"Randen"</string>
-    <string name="done" msgid="3112344807927554662">"Gereed"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Ongedaan maken"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Opnieuw"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Geschiedenis weerg."</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Geschiedenis verb."</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Afb.status weergeven"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Afb.status verbergen"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Toegepaste effecten weergeven"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Toegepaste effecten verbergen"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Instellingen"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Er zijn niet-opgeslagen wijzigingen aangebracht in deze afbeelding."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Wilt u opslaan voordat u afsluit?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Opslaan en sluiten"</string>
+    <string name="exit" msgid="242642957038770113">"Sluiten"</string>
     <string name="history" msgid="455767361472692409">"Geschiedenis"</string>
     <string name="reset" msgid="9013181350779592937">"Opnieuw instellen"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Huidige afbeeldingsstatus"</string>
+    <string name="imageState" msgid="8632586742752891968">"Toegepaste effecten"</string>
     <string name="compare_original" msgid="8140838959007796977">"Vergelijken"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Toepassen"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Opnieuw instellen"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Geen"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Vast"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Belichting"</string>
     <string name="sharpness" msgid="6463103068318055412">"Scherpte"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Auto-kleur"</string>
     <string name="hue" msgid="6231252147971086030">"Kleurschakering"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Schaduw"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Accenten"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curven"</string>
     <string name="vignette" msgid="934721068851885390">"Vervloeien"</string>
     <string name="redeye" msgid="4508883127049472069">"Rode ogen"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Tekenen"</string>
     <string name="straighten" msgid="26025591664983528">"Recht maken"</string>
     <string name="crop" msgid="5781263790107850771">"Bijsnijden"</string>
     <string name="rotate" msgid="2796802553793795371">"Draaien"</string>
     <string name="mirror" msgid="5482518108154883096">"Spiegelen"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatief"</string>
     <string name="none" msgid="6633966646410296520">"Geen"</string>
+    <string name="edge" msgid="7036064886242147551">"Randen"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Downsample"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rood"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Groen"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blauw"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stijl"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Grootte"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Kleur"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Lijnen"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Markeerstift"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spetters"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Wissen"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Aangepaste kleur kiezen"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Kleur selecteren"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Formaat selecteren"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Oorspronkelijk"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultaat"</string>
 </resources>
diff --git a/res/values-nl/photoeditor_strings.xml b/res/values-nl/photoeditor_strings.xml
deleted file mode 100644
index e00c715..0000000
--- a/res/values-nl/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Kan de foto niet laden"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Kan de bewerkte foto niet opslaan"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Bewerkte foto is opgeslagen in <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Niet-opgeslagen wijzigingen annuleren?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"OPSLAAN"</string>
-    <string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
-    <string name="crop" msgid="7598378507763334041">"Bijsnijden"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Kruisverwerking"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentaire"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duotone"</string>
-    <string name="facelift" msgid="6205748523156172637">"Egale teint"</string>
-    <string name="facetan" msgid="4412831806626044111">"Zongebruind"</string>
-    <string name="filllight" msgid="2644989991700022526">"Licht invullen"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Visooglens"</string>
-    <string name="flip" msgid="2357692401826287480">"Spiegelen"</string>
-    <string name="grain" msgid="7487585304579789098">"Korrelstructuur"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Z/W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Zwart/wit"</string>
-    <string name="highlight" msgid="3902653944386623972">"Lichter maken"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatief"</string>
-    <string name="posterize" msgid="4139212359561383385">"Postereffect"</string>
-    <string name="redeye" msgid="4958448806369928239">"Rode ogen"</string>
-    <string name="rotate" msgid="6607597269792373083">"Draaien"</string>
-    <string name="saturation" msgid="8621322012271169931">"Verzadiging"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Schaduwen"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Scherper maken"</string>
-    <string name="straighten" msgid="5217801513491493491">"Recht maken"</string>
-    <string name="temperature" msgid="1607987938521534517">"Warmte"</string>
-    <string name="tint" msgid="154435943863418434">"Tint"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vervloeiende randen"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Sleep de markeringen om de foto bij te snijden"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Teken op de foto om hierop te krabbelen"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Sleep de foto om deze te spiegelen"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Tik op rode ogen om ze te verwijderen"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Sleep de foto om deze te draaien"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Sleep de foto om deze recht te zetten"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Belichtingseffecten"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Kleureffecten"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Artistieke effecten"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Corrigeren"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Ongedaan maken"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Opnieuw"</string>
-</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index aa282fc..ed41cbd 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rechtsom draaien"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Kan item niet vinden."</string>
     <string name="edit" msgid="1502273844748580847">"Bewerken"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Eenvoudig bewerken"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Cacheverzoeken verwerken"</string>
     <string name="caching_label" msgid="4521059045896269095">"In cache opslaan..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Bijsnijden"</string>
     <string name="trim_action" msgid="703098114452883524">"Bijsnijden"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Dempen"</string>
     <string name="set_as" msgid="3636764710790507868">"Instellen als"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Kan video niet dempen."</string>
     <string name="video_err" msgid="7003051631792271009">"Video kan niet worden afgespeeld."</string>
     <string name="group_by_location" msgid="316641628989023253">"Op locatie"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Op tijd"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Autom."</string>
     <string name="flash_on" msgid="7891556231891837284">"Geflitst"</string>
     <string name="flash_off" msgid="1445443413822680010">"Geen flits"</string>
+    <string name="unknown" msgid="3506693015896912952">"Onbekend"</string>
     <string name="ffx_original" msgid="372686331501281474">"Origineel"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Geen externe opslag beschikbaar"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstrookweergave"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Rasterweergave"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Op volledig scherm"</string>
     <string name="trimming" msgid="9122385768369143997">"Bijsnijden"</string>
+    <string name="muting" msgid="5094925919589915324">"Dempen"</string>
     <string name="please_wait" msgid="7296066089146487366">"Een ogenblik geduld"</string>
-    <string name="save_into" msgid="4960537214388766062">"Bijgesneden video opslaan in album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Video opslaan in <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Kan niet bijsnijden: doelvideo is te kort"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panorama wordt gegenereerd"</string>
     <string name="save" msgid="613976532235060516">"Opslaan"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Inhoud scannen..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d items gescand"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item gescand"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d items gescand"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorteren..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scannen gereed"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importeren..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Er is geen inhoud om te importeren naar dit apparaat."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Er is geen MTP-apparaat verbonden"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Camerafout"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Kan geen verbinding maken met de camera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"De camera is uitgeschakeld vanwege het beveiligingsbeleid."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Camera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Camcorder"</string>
+    <string name="wait" msgid="8600187532323801552">"Een ogenblik geduld..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Koppel de USB-opslag voordat u de camera gebruikt."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Plaats een SD-kaart voordat u de camera gebruikt."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB-opslag voorbereiden…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD-kaart voorbereiden…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Geen toegang tot USB-opslag."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Geen toegang tot SD-kaart."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ANNULEREN"</string>
+    <string name="review_ok" msgid="1156261588693116433">"GEREED"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Time-lapse-opname"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Camera selecteren"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Achterzijde"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Voorzijde"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Locatie opslaan"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCATIE"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Afteltimer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 seconde"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d seconden"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Pieptoon bij aftellen"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Uit"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Aan"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videokwaliteit"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Hoog"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Laag"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Time-lapse"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Camera-instellingen"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Camcorder-instellingen"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Grootte van foto"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M pixels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M pixels (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Scherpstelmodus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatisch"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Oneindig"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATISCH"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"ONEINDIG"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flitsmodus"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"FLITSMODUS"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatisch"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Aan"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Uit"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLITSER AUTOMATISCH"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLITSER AAN"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLITSER UIT"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Witbalans"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"WITBALANS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatisch"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Gloeilamp"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Daglicht"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescerend"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Bewolkt"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATISCH"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"GLOEILAMP"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAGLICHT"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCEREND"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"BEWOLKT"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scènemodus"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatisch"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Actie"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nacht"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Zonsondergang"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Feest"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"GEEN"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACTIE"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NACHT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ZONSONDERGANG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FEEST"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"AFTELTIMER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TIMER UIT"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SECONDE"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SECONDEN"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SECONDEN"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SECONDEN"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Kan niet worden geselecteerd in scènemodus."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Belichting"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"BELICHTING"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CAMERA AAN VOORZIJDE"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CAMERA AAN ACHTERZIJDE"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Uw USB-opslag is bijna vol. Wijzig de kwaliteitsinstelling of verwijder enkele afbeeldingen of andere bestanden."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Uw SD-kaart is bijna vol. Wijzig de kwaliteitsinstelling of verwijder enkele afbeeldingen of andere bestanden."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Maximale grootte bereikt"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Te snel"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panorama voorbereiden"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Kan panorama niet opslaan."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panorama vastleggen"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Wachten op het vorige panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Opslaan…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panorama genereren"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Raak aan voor scherpstellen."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effecten"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Geen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Samendrukken"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Grote ogen"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Grote mond"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Kleine mond"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Grote neus"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Kleine ogen"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"In de ruimte"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Zonsondergang"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Uw video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Zet uw apparaat neer."\n"Stap even uit beeld."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Raak aan om een foto te maken tijdens een opname."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video-opname is gestart."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video-opname is gestopt."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Videosnapshot staat uit als speciale effecten zijn ingeschakeld."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Effecten wissen"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"GEKKE GEZICHTEN"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ACHTERGROND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Sluiterknop"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menuknop"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Meest recente foto"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Schakelen tussen camera aan voorzijde en aan achterzijde"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Camera-, video- of panoramakiezer"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Meer instelopties"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Instelopties sluiten"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoomregeling"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"%1$s verlagen"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"%1$s verhogen"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s selectievakje"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Overschakelen naar foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Overschakelen naar video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Overschakelen naar panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Overschakelen naar nieuwe panoramamodus"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Beoordeling: annuleren"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Beoordeling: gereed"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Opnieuw maken na beoordeling"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Video afspelen"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Video onderbreken"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Video opnieuw laden"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Tijdbalk videospeler"</string>
+    <string name="capital_on" msgid="5491353494964003567">"AAN"</string>
+    <string name="capital_off" msgid="7231052688467970897">"UIT"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Uit"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 seconde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 seconden"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuten"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 uur"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 uur"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"seconden"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuten"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"uren"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Gereed"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Tijdsinterval instellen"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Time-lapse-functie is uitgeschakeld. Schakel deze in om het tijdsinterval in te stellen."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Afteltimer is uitgeschakeld. Schakel de timer in om af te tellen vóór het nemen van een foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Duur in seconden instellen"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Aftellen om een foto te nemen"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Fotolocaties onthouden?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Label uw foto\'s en video\'s met de locaties waar ze zijn genomen."\n\n"Andere apps hebben toegang tot deze informatie en uw opgeslagen afbeeldingen."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nee, bedankt"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Zoeken"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foto\'s"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albums"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MEER OPTIES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"INSTELLINGEN"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foto\'s"</item>
+  </plurals>
 </resources>
diff --git a/res/values-notouch-v14/styles.xml b/res/values-notouch-v14/styles.xml
new file mode 100644
index 0000000..1b1b1af
--- /dev/null
+++ b/res/values-notouch-v14/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <style name="Theme.GalleryBase" parent="android:Theme.Holo.NoActionBar.Fullscreen">
+        <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
+        <item name="switchStyle">@android:style/Widget.CompoundButton</item>
+    </style>
+    <style name="ActionBarTwoLineItem">
+        <item name="android:background">?android:attr/activatedBackgroundIndicator</item>
+    </style>
+</resources>
diff --git a/res/values-pl/filtershow_strings.xml b/res/values-pl/filtershow_strings.xml
index d4a360a..2db5672 100644
--- a/res/values-pl/filtershow_strings.xml
+++ b/res/values-pl/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nie można wczytać zdjęcia."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Ustawiam tapetę"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Nie można pobrać zdjęcia. Sieć jest niedostępna."</string>
     <string name="original" msgid="3524493791230430897">"Oryginalny"</string>
     <string name="borders" msgid="2067345080568684614">"Granice"</string>
-    <string name="done" msgid="3112344807927554662">"Gotowe"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Cofnij"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Ponów"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Pokaż historię"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ukryj historię"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Pokaż stan obrazu"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ukryj stan obrazu"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Pokaż zastosowane efekty"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ukryj zastosowane efekty"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Ustawienia"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Ten obraz zawiera niezapisane zmiany."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Czy chcesz zapisać przed zamknięciem?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Zapisz i zamknij"</string>
+    <string name="exit" msgid="242642957038770113">"Zamknij"</string>
     <string name="history" msgid="455767361472692409">"Historia"</string>
     <string name="reset" msgid="9013181350779592937">"Resetuj"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Obecny stan obrazu"</string>
+    <string name="imageState" msgid="8632586742752891968">"Zastosowane efekty"</string>
     <string name="compare_original" msgid="8140838959007796977">"Porównaj"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Zastosuj"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Resetuj"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Brak"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Stałe"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Mała planetka"</string>
     <string name="exposure" msgid="6526397045949374905">"Ekspozycja"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ostrość"</string>
@@ -56,19 +60,38 @@
     <string name="vibrance" msgid="3326744578577835915">"Wibrancja"</string>
     <string name="saturation" msgid="7026791551032438585">"Nasycenie"</string>
     <string name="bwfilter" msgid="8927492494576933793">"Filtr cz-b"</string>
-    <string name="wbalance" msgid="6346581563387083613">"Automatyczny kolor"</string>
+    <string name="wbalance" msgid="6346581563387083613">"Autokolor"</string>
     <string name="hue" msgid="6231252147971086030">"Odcień"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Cienie"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Podświetlenie"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Krzywe"</string>
     <string name="vignette" msgid="934721068851885390">"Winietowanie"</string>
     <string name="redeye" msgid="4508883127049472069">"Czerwone oczy"</string>
-    <string name="straighten" msgid="26025591664983528">"Wyprostowanie"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Rysuj"</string>
+    <string name="straighten" msgid="26025591664983528">"Wyprostuj"</string>
     <string name="crop" msgid="5781263790107850771">"Przycinanie"</string>
-    <string name="rotate" msgid="2796802553793795371">"Obrót"</string>
+    <string name="rotate" msgid="2796802553793795371">"Obróć"</string>
     <string name="mirror" msgid="5482518108154883096">"Odbicie"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatyw"</string>
     <string name="none" msgid="6633966646410296520">"Brak"</string>
+    <string name="edge" msgid="7036064886242147551">"Krawędzie"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Zmniejsz"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Czerwony"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zielony"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Niebieski"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Styl"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Rozmiar"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Kolor"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linie"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Rozprysk"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Wyczyść"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Wybierz własny kolor"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Wybierz kolor"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Wybierz rozmiar"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Oryginał"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Wynik"</string>
 </resources>
diff --git a/res/values-pl/photoeditor_strings.xml b/res/values-pl/photoeditor_strings.xml
deleted file mode 100644
index a26f694..0000000
--- a/res/values-pl/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Studio fotograficzne"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Nie można wczytać zdjęcia"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Nie można zapisać edytowanego zdjęcia"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Edytowane zdjęcie zapisano w <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Odrzucić niezapisane zmiany?"</string>
-    <string name="yes" msgid="5402582493291792293">"Tak"</string>
-    <string name="save" msgid="5516670392524294967">"ZAPISZ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Autopoprawka"</string>
-    <string name="crop" msgid="7598378507763334041">"Przytnij"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proces krosowy"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
-    <string name="doodle" msgid="1686409894518940990">"Bazgroły"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dwuton"</string>
-    <string name="facelift" msgid="6205748523156172637">"Wygładzanie"</string>
-    <string name="facetan" msgid="4412831806626044111">"Opalenizna"</string>
-    <string name="filllight" msgid="2644989991700022526">"Doświetlenie"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Rybie oko"</string>
-    <string name="flip" msgid="2357692401826287480">"Odwrócenie"</string>
-    <string name="grain" msgid="7487585304579789098">"Ziarno kliszy"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Czerń i biel"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Czarno-białe"</string>
-    <string name="highlight" msgid="3902653944386623972">"Podświetlenie"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Łomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatyw"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posteryzacja"</string>
-    <string name="redeye" msgid="4958448806369928239">"Czerwone oczy"</string>
-    <string name="rotate" msgid="6607597269792373083">"Obrót"</string>
-    <string name="saturation" msgid="8621322012271169931">"Nasycenie"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Cienie"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Wyostrzenie"</string>
-    <string name="straighten" msgid="5217801513491493491">"Wyprostowanie"</string>
-    <string name="temperature" msgid="1607987938521534517">"Ciepło"</string>
-    <string name="tint" msgid="154435943863418434">"Odcień"</string>
-    <string name="vignette" msgid="7648125924662648282">"Winietowanie"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Przeciągnij znaczniki, aby przyciąć zdjęcie"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Rysuj po zdjęciu, aby dodać bazgroły."</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Przeciągnij zdjęcie, aby je odwrócić."</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Dotknij czerwonych oczu, aby je usunąć"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Przeciągnij zdjęcie, aby je obrócić."</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Przeciągnij zdjęcie, aby je wyprostować."</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efekty ekspozycji"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efekty kolorystyczne"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efekty artystyczne"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Popraw"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Cofnij"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Ponów"</string>
-</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b6f70fd..f55efc5 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Obróć w prawo"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Nie można znaleźć elementu."</string>
     <string name="edit" msgid="1502273844748580847">"Edytuj"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Prosta edycja"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Przetwarzanie żądań dotyczących buforowania"</string>
     <string name="caching_label" msgid="4521059045896269095">"Buforowanie…"</string>
     <string name="crop_action" msgid="3427470284074377001">"Przytnij"</string>
     <string name="trim_action" msgid="703098114452883524">"Przytnij"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Wycisz"</string>
     <string name="set_as" msgid="3636764710790507868">"Ustaw jako"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Nie można wyciszyć filmu."</string>
     <string name="video_err" msgid="7003051631792271009">"Nie można odtworzyć filmu."</string>
     <string name="group_by_location" msgid="316641628989023253">"Według lokalizacji"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Według daty"</string>
@@ -141,15 +144,16 @@
     <string name="auto" msgid="4296941368722892821">"Automat."</string>
     <string name="flash_on" msgid="7891556231891837284">"Z lampą"</string>
     <string name="flash_off" msgid="1445443413822680010">"Bez lampy"</string>
+    <string name="unknown" msgid="3506693015896912952">"Nieznane"</string>
     <string name="ffx_original" msgid="372686331501281474">"Oryginalny"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Zdjęcie błyskawiczne"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Wybielacz"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Niebieski"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"Czarno-biały"</string>
     <string name="ffx_punch" msgid="1343475517872562639">"Stempel"</string>
-    <string name="ffx_x_process" msgid="4779398678661811765">"Obróbka crossowa"</string>
-    <string name="ffx_washout" msgid="4594160692176642735">"Kawa z mlekiem"</string>
+    <string name="ffx_x_process" msgid="4779398678661811765">"Cross"</string>
+    <string name="ffx_washout" msgid="4594160692176642735">"Latte"</string>
     <string name="ffx_washout_color" msgid="8034075742195795219">"Litografia"</string>
   <plurals name="make_albums_available_offline">
     <item quantity="one" msgid="2171596356101611086">"Udostępnianie albumu w trybie offline"</item>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Brak pamięci zewnętrznej"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Widok taśmy filmowej"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Widok siatki"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Widok pełnoekranowy"</string>
     <string name="trimming" msgid="9122385768369143997">"Przycinam"</string>
+    <string name="muting" msgid="5094925919589915324">"Wyciszam"</string>
     <string name="please_wait" msgid="7296066089146487366">"Poczekaj"</string>
-    <string name="save_into" msgid="4960537214388766062">"Zapisuję przycięty film w albumie:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Zapisuję film w albumie <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nie można przyciąć: film docelowy jest za krótki"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Renderuję panoramę"</string>
     <string name="save" msgid="613976532235060516">"Zapisz"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skanuję zawartość..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d przeskanowanych elementów"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d element przeskanowany"</item>
+    <item quantity="other" msgid="3138021473860555499">"Przeskanowane elementy: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sortuję..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skanowanie ukończone"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importuję..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Brak treści do zaimportowania na tym urządzeniu."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Brak podłączonego urządzenia MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Błąd aparatu"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nie można nawiązać połączenia z aparatem."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Aparat został wyłączony z powodu zasad bezpieczeństwa."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Aparat"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Kamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Proszę czekać…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Zanim użyjesz aparatu podłącz nośnik USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Przed przystąpieniem do korzystania z aparatu włóż kartę SD."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Przygotowywanie nośnika USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Przygotowywanie karty SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nie można uzyskać dostępu do pamięci USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nie można uzyskać dostępu do karty SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ANULUJ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"GOTOWE"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Nagrywanie poklatkowe"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Wybierz aparat"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Tył"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Przód"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Zapis lokalizacji"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKALIZACJA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Samowyzwalacz"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekunda"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d s"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Sygnał odliczania"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Wyłączono"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Włączono"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Jakość wideo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Wysoka"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Niska"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Upływ czasu"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Ustawienia aparatu"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Ustawienia kamery"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Rozmiar zdjęcia"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapikseli"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapikseli"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 Mpix"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapiksele"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 Mpix"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 Mpix"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 megapiksele (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 Mpix"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 Mpix"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Tryb ostrości"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Nieskończoność"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMATYCZNIE"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"NIESKOŃCZONOŚĆ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Lampa błyskowa"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"LAMPA BŁYSKOWA"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatyczna"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Włączona"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Wyłączona"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"LAMPA BŁYSKOWA AUTOMATYCZNIE"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"LAMPA BŁYSKOWA WŁĄCZONA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"LAMPA BŁYSKOWA WYŁĄCZONA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balans bieli"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANS BIELI"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Auto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Światło żarowe"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Światło dzienne"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescencja"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Zachmurzenie"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMATYCZNY"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ŚWIATŁO ŻAROWE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ŚWIATŁO DZIENNE"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENCJA"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ZACHMURZENIE"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Tryb scenerii"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Auto"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Akcja"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noc"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Zachód słońca"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Impreza"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"BRAK"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AKCJA"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOC"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ZACHÓD SŁOŃCA"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ZABAWA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"SAMOWYZWALACZ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"SAMOWYZWALACZ WYŁ."</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDA"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDY"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUND"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUND"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Opcja niedostępna w trybie scenerii."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Ekspozycja"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EKSPOZYCJA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PRZEDNI APARAT"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"TYLNY APARAT"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Na nośniku USB kończy się miejsce. Zmień ustawienie jakości bądź usuń niektóre zdjęcia lub inne pliki."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Na karcie SD kończy się miejsce. Zmień ustawienie jakości bądź usuń niektóre zdjęcia lub inne pliki."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Osiągnięto limit rozmiaru."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Zbyt szybko"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Przygotowywanie panoramy"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Nie można zapisać panoramy."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Tworzenie panoramy"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Oczekiwanie na poprzednią panoramę"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Zapis..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Renderowanie panoramy"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Ustaw ostrość, klikając."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efekty"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Brak"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Ściśnięcie"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Wielkie oczy"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Wielkie usta"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Małe usta"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Wielki nos"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Małe oczy"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"W kosmosie"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Zachód słońca"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Twój film"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Skieruj urządzenie w dół."\n"Opuść na chwilę kadr."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Dotknij, by zrobić zdjęcie podczas nagrywania."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Rozpoczęto nagrywanie filmu."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Zatrzymano nagrywanie filmu."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Stopklatka nie działa, gdy są aktywne efekty specjalne."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Usuń efekty"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ZABAWNE TWARZE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"TŁO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Przycisk migawki"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Przycisk menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Najnowsze zdjęcie"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Przełącznik przedniego i tylnego aparatu"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Wybór aparatu, filmu lub panoramy"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Więcej ustawień"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Zamknij ustawienia"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Sterowanie powiększeniem"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Zmniejsz: %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Zwiększ: %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Pole wyboru %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Przełącz na zdjęcia"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Przełącz na wideo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Przełącz na panoramę"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Przełącz na nową panoramę"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Przegląd: anulowanie"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Przegląd: gotowe"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Przegląd – zrób zdjęcie/nagraj film ponownie"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Odtwórz film"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Wstrzymaj film"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Załaduj ponownie film"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Pasek czasu w odtwarzaczu wideo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"WŁĄCZONY"</string>
+    <string name="capital_off" msgid="7231052688467970897">"WYŁĄCZONY"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Wył."</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuty"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 godzina"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 godziny"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 godzin"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 godz."</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 godzin"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 godzin"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 godzin"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 godziny"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sek."</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"min"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"godz."</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Gotowe"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Ustaw interwał czasu"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funkcja filmu poklatkowego jest wyłączona. Włącz ją, by ustawić interwał czasu."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Samowyzwalacz jest wyłączony. Włącz go, by odliczał czas pozostały do wykonania zdjęcia."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Ustaw czas w sekundach"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Odliczanie czasu pozostałego do zrobienia zdjęcia"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Zapamiętywać lokalizacje zdjęć?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Oznacz zdjęcia i filmy informacją, gdzie zostały zrobione."\n\n"Inne aplikacje mają dostęp do tych informacji wraz z zapisanymi zdjęciami."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nie, dziękuję"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Tak"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Aparat"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Szukaj"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Zdjęcia"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumy"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"WIĘCEJ OPCJI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"USTAWIENIA"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d zdjęcie"</item>
+    <item quantity="other" msgid="3813306834113858135">"Zdjęcia: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-port/styles.xml b/res/values-port/styles.xml
new file mode 100644
index 0000000..46871c6
--- /dev/null
+++ b/res/values-port/styles.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="ReviewControlIcon">
+        <item name="android:layout_height">@dimen/switcher_size</item>
+        <item name="android:layout_width">@dimen/switcher_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:layout_centerVertical">true</item>
+        <item name="android:clickable">true</item>
+        <item name="android:focusable">true</item>
+        <item name="android:background">@drawable/bg_pressed</item>
+    </style>
+    <style name="SettingPopupWindow">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:layout_marginBottom">@dimen/setting_popup_right_margin</item>
+        <item name="android:visibility">gone</item>
+    </style>
+    <style name="PopupTitleText">
+        <item name="android:textSize">@dimen/popup_title_text_size</item>
+        <item name="android:layout_gravity">left|center_vertical</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">@color/popup_title_color</item>
+        <item name="android:layout_marginLeft">10dp</item>
+    </style>
+    <style name="ViewfinderLabelLayout">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_marginTop">13dp</item>
+        <item name="android:layout_marginBottom">@dimen/indicator_bar_width</item>
+        <item name="android:layout_marginLeft">13dp</item>
+        <item name="android:layout_marginRight">13dp</item>
+    </style>
+    <style name="PanoViewHorizontalBar">
+        <item name="android:background">#000000</item>
+        <item name="android:alpha">1.0</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">0dp</item>
+        <item name="android:layout_weight">1</item>
+    </style>
+    <style name="SettingPopupWindow_xlarge">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:layout_alignParentBottom">true</item>
+        <item name="android:layout_marginBottom">@dimen/setting_popup_right_margin</item>
+        <item name="android:visibility">gone</item>
+    </style>
+</resources>
diff --git a/res/values-pt-rPT/filtershow_strings.xml b/res/values-pt-rPT/filtershow_strings.xml
index ff4c930..4be20a5 100644
--- a/res/values-pt-rPT/filtershow_strings.xml
+++ b/res/values-pt-rPT/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Não é possível carregar a imagem!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"A definir imagem de fundo"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Não foi possível transferir a fotografia. Rede não disponível."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Limites"</string>
-    <string name="done" msgid="3112344807927554662">"Concluído"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Anular"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Refazer"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostrar Histórico"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ocultar Histórico"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Ver Estado da Imagem"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ocul. Estado Imagem"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostrar Efeitos Aplicados"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ocultar Efeitos Aplicados"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Definições"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Existem alterações a esta imagem não guardadas."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Pretende guardar antes de sair?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Guardar e Sair"</string>
+    <string name="exit" msgid="242642957038770113">"Sair"</string>
     <string name="history" msgid="455767361472692409">"Histórico"</string>
     <string name="reset" msgid="9013181350779592937">"Repor"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Estado da Imagem Atual"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efeitos Aplicados"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Repor"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nenhum"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fixo"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planeta Minúsculo"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposição"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidez"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
     <string name="hue" msgid="6231252147971086030">"Tonalidade"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Destaques"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Vinheta"</string>
     <string name="redeye" msgid="4508883127049472069">"Olhos Vermelhos"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Desenhar"</string>
     <string name="straighten" msgid="26025591664983528">"Endireitar"</string>
     <string name="crop" msgid="5781263790107850771">"Recortar"</string>
     <string name="rotate" msgid="2796802553793795371">"Rodar"</string>
     <string name="mirror" msgid="5482518108154883096">"Espelho"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativo"</string>
     <string name="none" msgid="6633966646410296520">"Nenhum"</string>
+    <string name="edge" msgid="7036064886242147551">"Limites"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Subamostra"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Vermelho"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Azul"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estilo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Tamanho"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Cor"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linhas"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marcador"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Salpicar"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Limpar"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Selecionar cor personalizada"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Selecionar a cor"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Selecionar Tamanho"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultado"</string>
 </resources>
diff --git a/res/values-pt-rPT/photoeditor_strings.xml b/res/values-pt-rPT/photoeditor_strings.xml
deleted file mode 100644
index e4dd52f..0000000
--- a/res/values-pt-rPT/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Não foi possível carregar a fotografia"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Não foi possível guardar a foto editada."</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Fotografia editada guardada em <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Rejeitar alterações não guardadas?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sim"</string>
-    <string name="save" msgid="5516670392524294967">"GUARDAR"</string>
-    <string name="autofix" msgid="1663414996270538748">"Correção Autom."</string>
-    <string name="crop" msgid="7598378507763334041">"Recortar"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Processo cruzado"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentário"</string>
-    <string name="doodle" msgid="1686409894518940990">"Rabiscar"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dois tons"</string>
-    <string name="facelift" msgid="6205748523156172637">"Rosto Brilhante"</string>
-    <string name="facetan" msgid="4412831806626044111">"Rosto Bronzeado"</string>
-    <string name="filllight" msgid="2644989991700022526">"Preench. Claro"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Olho de peixe"</string>
-    <string name="flip" msgid="2357692401826287480">"Inverter"</string>
-    <string name="grain" msgid="7487585304579789098">"Filme Granulado"</string>
-    <string name="grayscale" msgid="7641563843060945228">"P&amp;B"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Preto e branco"</string>
-    <string name="highlight" msgid="3902653944386623972">"Realces"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
-    <string name="posterize" msgid="4139212359561383385">"Póster"</string>
-    <string name="redeye" msgid="4958448806369928239">"Olhos Vermelhos"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rodar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturação"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Aumentar nitidez"</string>
-    <string name="straighten" msgid="5217801513491493491">"Endireitar"</string>
-    <string name="temperature" msgid="1607987938521534517">"Cores Quentes"</string>
-    <string name="tint" msgid="154435943863418434">"Tonalidade"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinheta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Arraste os marcadores para recortar a fotografia"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na fotografia para rabiscar"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Arraste a fotografia para virá-la"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Toque nos olhos vermelhos p/ os remover"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a fotografia para rodá-la"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a fotografia para endireitá-la"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Os efeitos da exposição"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efeitos de cor"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efeitos artísticos"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Corrigir"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Anular"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Refazer"</string>
-</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index f9d4e63..1825036 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rodar para a direita"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Não foi possível encontrar item."</string>
     <string name="edit" msgid="1502273844748580847">"Editar"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edição Simples"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"A processar pedidos de colocação em cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"A col. cache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Recortar"</string>
     <string name="trim_action" msgid="703098114452883524">"Recortar"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Desativar som"</string>
     <string name="set_as" msgid="3636764710790507868">"Definir como"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Impossível silenciar o vídeo."</string>
     <string name="video_err" msgid="7003051631792271009">"Não é possível reproduzir vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Por localização"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Por hora"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Automático"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash dispar."</string>
     <string name="flash_off" msgid="1445443413822680010">"Sem flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Desconhecida"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Instantâneo"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Bleach"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Azul"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"P/B"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nenhum armazenamento externo disponível"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Vista de película"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Vista de grelha"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Vista ecrã inteiro"</string>
     <string name="trimming" msgid="9122385768369143997">"Recorte"</string>
+    <string name="muting" msgid="5094925919589915324">"A desativar o som"</string>
     <string name="please_wait" msgid="7296066089146487366">"Aguarde"</string>
-    <string name="save_into" msgid="4960537214388766062">"A guardar vídeo recortado no álbum:"</string>
+    <string name="save_into" msgid="9155488424829609229">"A guardar o vídeo em <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Não é possível recortar: o vídeo de destino é demasiado pequeno"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"A compor panorama"</string>
     <string name="save" msgid="613976532235060516">"Guardar"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"A digitalizar o conteúdo..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d itens digitalizados"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item digitalizado"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d itens digitalizados"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"A ordenar..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Digitalização concluída"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"A importar..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Não existe conteúdo disponível para importar neste dispositivo."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Não estão ligados dispositivos MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Erro da câmara"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Não é possível efetuar ligação à câmara."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Devido a políticas de segurança, a câmara foi desativada."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Câmara"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Câmara de vídeo"</string>
+    <string name="wait" msgid="8600187532323801552">"Aguarde..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Monte a memória de armazenamento USB antes de utilizar a câmara."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Insira um cartão SD antes de utilizar a câmara."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparar armazenamento USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"A preparar o cartão SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Não foi possível aceder à memória de armazenamento USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Não foi possível aceder ao cartão SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCELAR"</string>
+    <string name="review_ok" msgid="1156261588693116433">"CONCLUÍDO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Gravação com intervalo de tempo"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Escolher câmara"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Traseira"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Frontal"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Incluir localização"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCALIZAÇÃO"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Temporizador de contagem decr."</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 segundo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d segundos"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Apitar durante a contagem descrescente"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Desativado"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Ativado"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualidade de vídeo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Baixa"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervalo de tempo"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Definições da câmara"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Definições da câmara de vídeo"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Tamanho da imagem"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 MP"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapíxeis"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 MP"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapíxeis"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapíxeis"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 MP (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapíxeis"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapíxel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Modo de focagem"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automático"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinito"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMÁTICO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Modo flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODO FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automático"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Ativado"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Desativado"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTOMÁTICO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH ATIVADO"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DESATIVADO"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Equilíbrio dos brancos"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"EQUILÍBRIO DOS BRANCOS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automático"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescente"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Luz do dia"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescente"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nublado"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMÁTICO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUZ DO DIA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUBLADO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modo cenário"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automático"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acção"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Nocturno"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Ocaso"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Partido"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NENHUM"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AÇÃO"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOITE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"PÔR DO SOL"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"TEMPORIZADOR DE CONTAGEM DECRESCENTE"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORIZADOR DESATIVADO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGUNDOS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Não selecionável no modo de cena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposição"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSIÇÃO"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CÂMARA FRONTAL"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CÂMARA POSTERIOR"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Está a ficar sem espaço no armazenamento USB. Altere as definições de qualidade ou elimine algumas imagens ou outros ficheiros."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Está a ficar sem espaço no cartão SD. Altere as definições de qualidade ou elimine algumas imagens ou outros ficheiros."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Limite de tamanho atingido."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Muito rápido"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"A preparar panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Não foi possível guardar panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"A tirar foto de panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"A aguardar panorama anterior"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"A guardar..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"A compor panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Toque para focar."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efeitos"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Nenhum"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Comprimir"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Olhos grandes"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Boca grande"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Boca pequena"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nariz grande"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Olhos pequenos"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"No espaço"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Pôr do Sol"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"O seu vídeo"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Vire o seu aparelho para baixo."\n"Saia da vista por alguns momentos."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Toque para tirar uma fotografia durante a gravação."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"A gravação de vídeo foi iniciada."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"A gravação de vídeo foi parada."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Instantâneo vídeo desat. quando efeitos especiais estão ativos."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Limpar efeitos"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"CARETAS"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FUNDO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Botão Obturador"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Botão de menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Fotografia mais recente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Interruptor da câmara frontal e posterior"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Seletor de câmara, vídeo ou panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Mais controlos de definições"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Fechar controlos de definições"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Controlo de zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Diminuir %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Aumentar %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Caixa de verificação %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Mudar para fotografia"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Mudar para vídeo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Mudar para panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Mudar para o novo panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Comentário cancelado."</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Reveja o que está concluído"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Retomar comentário"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Reproduzir vídeo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Interromper vídeo"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Atualizar vídeo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra de tempo do leitor de vídeo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ATIVADA"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DESATIVADA"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Desativado"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 horas"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"segundos"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutos"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"horas"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Concluído"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Definir Intervalo de Tempo"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"A funcionalidade de intervalo de tempo está desativada. Ative-a para definir o intervalo de tempo."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"O temporizador de contagem decrescente está desativado. Volte a ativá-lo para efetuar a contagem antes de tirar uma fotografia."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Definir a duração em segundos"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Contagem decrescente para tirar uma fotografia"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Memorizar localizações das fotografias?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Insira etiquetas nas suas fotografias e vídeos com as localizações onde foram capturados."\n\n"Outras aplicações podem aceder a estas informações juntamente com as suas imagens guardadas."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Não, obrigado"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sim"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Câmara"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Pesquisa"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotografias"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Álbuns"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MAIS OPÇÕES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"DEFINIÇÕES"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotografia"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotog."</item>
+  </plurals>
 </resources>
diff --git a/res/values-pt/filtershow_strings.xml b/res/values-pt/filtershow_strings.xml
index 4dd44bc..d54b040 100644
--- a/res/values-pt/filtershow_strings.xml
+++ b/res/values-pt/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Não é possível carregar a imagem!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Definindo plano de fundo"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Não foi possível fazer o download da foto. Rede indisponível."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Bordas"</string>
-    <string name="done" msgid="3112344807927554662">"Concluir"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Desfazer"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Refazer"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Mostrar histórico"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ocultar histórico"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Mostrar estado"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ocultar estado"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Mostrar efeitos aplicados"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ocultar efeitos aplicados"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Configurações"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Existem alterações não salvas nesta imagem."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Deseja salvar antes de sair?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Salvar e sair"</string>
+    <string name="exit" msgid="242642957038770113">"Sair"</string>
     <string name="history" msgid="455767361472692409">"Histórico"</string>
     <string name="reset" msgid="9013181350779592937">"Restaurar"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Estado atual da imagem"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efeitos aplicados"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparar"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplicar"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Restaurar"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Nenhuma"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fixo"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planetinha"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposição"</string>
     <string name="sharpness" msgid="6463103068318055412">"Nitidez"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Cor automática"</string>
     <string name="hue" msgid="6231252147971086030">"Matiz"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sombras"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Destaques"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curvas"</string>
     <string name="vignette" msgid="934721068851885390">"Vinheta"</string>
     <string name="redeye" msgid="4508883127049472069">"Olhos vermelhos"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Desenhar"</string>
     <string name="straighten" msgid="26025591664983528">"Endireitar"</string>
     <string name="crop" msgid="5781263790107850771">"Cortar"</string>
     <string name="rotate" msgid="2796802553793795371">"Girar"</string>
     <string name="mirror" msgid="5482518108154883096">"Espelhar"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativo"</string>
     <string name="none" msgid="6633966646410296520">"Nenhum"</string>
+    <string name="edge" msgid="7036064886242147551">"Bordas"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Diminuir"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Vermelho"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Azul"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estilo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Tamanho"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Cor"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linhas"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marcador"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Respingo"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Limpar"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Escolha uma cor personalizada"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Selecionar cor"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Selecionar tamanho"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultado"</string>
 </resources>
diff --git a/res/values-pt/photoeditor_strings.xml b/res/values-pt/photoeditor_strings.xml
deleted file mode 100644
index 9307081..0000000
--- a/res/values-pt/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Não foi possível carregar a foto"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Não foi possível salvar a foto editada"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"A foto editada foi salva em <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Descartar as alterações não salvas?"</string>
-    <string name="yes" msgid="5402582493291792293">"Sim"</string>
-    <string name="save" msgid="5516670392524294967">"SALVAR"</string>
-    <string name="autofix" msgid="1663414996270538748">"Correção autom."</string>
-    <string name="crop" msgid="7598378507763334041">"Cortar"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Proc. cruzado"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentário"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duotone"</string>
-    <string name="facelift" msgid="6205748523156172637">"Rosto iluminado"</string>
-    <string name="facetan" msgid="4412831806626044111">"Rosto bronzeado"</string>
-    <string name="filllight" msgid="2644989991700022526">"Luz de preench."</string>
-    <string name="fisheye" msgid="6037488646928998921">"Olho de peixe"</string>
-    <string name="flip" msgid="2357692401826287480">"Inverter"</string>
-    <string name="grain" msgid="7487585304579789098">"Granulado"</string>
-    <string name="grayscale" msgid="7641563843060945228">"P&amp;B"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Preto e branco"</string>
-    <string name="highlight" msgid="3902653944386623972">"Destaques"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativo"</string>
-    <string name="posterize" msgid="4139212359561383385">"Transf. em pôster"</string>
-    <string name="redeye" msgid="4958448806369928239">"Olhos vermelhos"</string>
-    <string name="rotate" msgid="6607597269792373083">"Girar"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturação"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sépia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sombras"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Aument. nitidez"</string>
-    <string name="straighten" msgid="5217801513491493491">"Endireitar"</string>
-    <string name="temperature" msgid="1607987938521534517">"Calor"</string>
-    <string name="tint" msgid="154435943863418434">"Tingir"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinheta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Arraste os marcadores para cortar a foto"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenhe na foto para rabiscar"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Arraste a foto para invertê-la"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Toque nos olhos vermelhos para removê-los"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Arraste a foto para girá-la"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Arraste a foto para endireitá-la"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efeitos de exposição"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efeitos de cor"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efeitos artísticos"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Corrigir"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Desfazer"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Refazer"</string>
-</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 88a23c6..67f4e92 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Girar para a direita"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Não foi possível encontrar o item."</string>
     <string name="edit" msgid="1502273844748580847">"Editar"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Edição simples"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Processando solicitações de armazenamento em cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Cache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Cortar"</string>
     <string name="trim_action" msgid="703098114452883524">"Cortar"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Desativar som"</string>
     <string name="set_as" msgid="3636764710790507868">"Definir como"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Impossível silenciar o vídeo."</string>
     <string name="video_err" msgid="7003051631792271009">"Não é possível reproduzir o vídeo."</string>
     <string name="group_by_location" msgid="316641628989023253">"Por local"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Por tempo"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash ativo"</string>
     <string name="flash_off" msgid="1445443413822680010">"Sem flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Desconhecido"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instant."</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Branqueamento"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Azul"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"P&amp;B"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nenhum armazenamento externo disponível"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Visualização de película"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Visualização de grade"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Visual. tela inteira"</string>
     <string name="trimming" msgid="9122385768369143997">"Cortando"</string>
+    <string name="muting" msgid="5094925919589915324">"Desativando som"</string>
     <string name="please_wait" msgid="7296066089146487366">"Aguarde"</string>
-    <string name="save_into" msgid="4960537214388766062">"Salvando vídeo cortado no álbum:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Salvando vídeo em <xliff:g id="ALBUM_NAME">%1$s</xliff:g>..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"Impossível cortar: o vídeo de destino é curto demais"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Renderizando panorama"</string>
     <string name="save" msgid="613976532235060516">"Salvar"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Verificando conteúdo..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d itens verificados"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d item verificado"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d itens verificados"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Classificando..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Verificação concluída"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importando..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nenhum conteúdo disponível para importação no dispositivo."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nenhum dispositivo MTP conectado"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Erro de câmera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Não é possível conectar à câmera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"A câmera foi desativada devido às políticas de segurança."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Câmera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Filmadora"</string>
+    <string name="wait" msgid="8600187532323801552">"Aguarde..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Monte o armazenamento USB antes de usar a câmera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Insira um cartão SD antes de usar a câmera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Preparando armazenamento USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparando o cartão SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Não foi possível acessar o armazenamento USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Não foi possível acessar o cartão SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"CANCELAR"</string>
+    <string name="review_ok" msgid="1156261588693116433">"CONCLUÍDO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Gravação de lapso de tempo"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Escolher câmera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Traseira"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Visão frontal"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Local de armazenamento"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCAL"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Contagem regressiva"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"Um segundo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d segundos"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Aviso sonoro"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Desativado"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Ativado"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualidade do vídeo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Alta"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Baixa"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Passagem de tempo"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Configurações da câmera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Configurações da filmadora"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Tamanho da imagem"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 MP"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 MP"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixels"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixels"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Modo de foco"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automático"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinito"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Modo de flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MODO DE FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automático"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Ativado"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Desativado"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLASH LIGADO"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLASH DESLIGADO"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balanço de branco"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANÇO DE BRANCO"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automático"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescente"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Luz do dia"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescente"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nublado"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUZ DO DIA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENTE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NUBLADO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modo de cena"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automático"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Ação"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Cena noturna"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Pôr-do-sol"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Festa"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NENHUM"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AÇÃO"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOITE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"PÔR-DO-SOL"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FESTA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"CONTAGEM REGRESSIVA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORIZADOR DESLIGADO"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGUNDOS"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGUNDOS"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Não pode ser selecionado no modo de cena."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposição"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSIÇÃO"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CÂMERA FRONTAL"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CÂMERA TRASEIRA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Seu armazenamento USB está sem espaço. Altere a configuração de qualidade ou exclua algumas imagens ou outros arquivos."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Seu cartão SD está sem espaço. Altere a configuração de qualidade ou exclua algumas imagens ou outros arquivos."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Limite de tamanho atingido."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Muito rápido"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Preparando panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Não foi possível salvar panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Capturando panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Aguardando panorama anterior"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Salvando..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Renderizando panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Toque para ajustar o foco."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efeitos"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Nenhum"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Comprimir"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Olhos grandes"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Boca grande"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Boca pequena"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nariz grande"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Olhos pequenos"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"No espaço"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Pôr-do-sol"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Seu vídeo"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Abaixe seu dispositivo."\n"Saia de vista por um momento."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Toque para tirar uma foto durante a gravação."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"A gravação de vídeo foi iniciada."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"A gravação de vídeo foi interrompida."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"O instant. de vídeo é desat. quando os efeitos esp. estão ativad."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Clarear efeitos"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"CARETAS"</string>
+    <string name="effect_background" msgid="6579360207378171022">"SEGUNDO PLANO"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Botão \"Tirar foto\""</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Botão de menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Foto mais recente"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Botão da câmera frontal e traseira"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Seletor de câmera, vídeo ou panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Mais controles de ajuste"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Fechar controles de ajuste"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Controle de zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Diminuir %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Aumentar %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"caixa de seleção %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Alternar para foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Alternar para vídeo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Alterar para panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Alterar para panorama novo"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Cancelar"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Revisão concluída"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Repetir anexo"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Reproduzir vídeo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pausar vídeo"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Atualizar vídeo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Barra de tempo do player de vídeo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"LIGADO"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DESLIGADO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Desligado"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 segundos"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minutos"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 hora"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 horas"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 horas"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"segundos"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minutos"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"horas"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Concluído"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Definir intervalo de tempo"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"O recurso de passagem de tempo está desativado. Ative-o para definir o intervalo de tempo."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"A contagem regressiva está desativada. Ative-a para fazer uma contagem regressiva antes de tirar uma foto."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Definir duração em segundos"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Contagem regressiva para tirar foto"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Memorizar locais de fotos?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Marque seus vídeos e fotos com os locais onde foram gerados."\n\n"Outros aplicativos podem acessar essas informações juntamente com suas imagens salvas."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Não, obrigado"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Sim"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Câmera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Pesquisar"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotos"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Álbuns"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MAIS OPÇÕES"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"CONFIGURAÇÕES"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotos"</item>
+  </plurals>
 </resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index eac44f0..dc98330 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -112,6 +112,8 @@
     <skip />
     <!-- no translation found for edit (1502273844748580847) -->
     <skip />
+    <!-- no translation found for simple_edit (2792835918347498211) -->
+    <skip />
     <!-- no translation found for process_caching_requests (8722939570307386071) -->
     <skip />
     <!-- no translation found for caching_label (4521059045896269095) -->
@@ -120,7 +122,11 @@
     <skip />
     <!-- no translation found for trim_action (703098114452883524) -->
     <skip />
+    <!-- no translation found for mute_action (5296241754753306251) -->
+    <skip />
     <string name="set_as" msgid="3636764710790507868">"Definir sco"</string>
+    <!-- no translation found for video_mute_err (6392457611270600908) -->
+    <skip />
     <!-- no translation found for video_err (7003051631792271009) -->
     <skip />
     <!-- no translation found for group_by_location (316641628989023253) -->
@@ -215,6 +221,8 @@
     <skip />
     <!-- no translation found for flash_off (1445443413822680010) -->
     <skip />
+    <!-- no translation found for unknown (3506693015896912952) -->
+    <skip />
     <!-- no translation found for ffx_original (372686331501281474) -->
     <skip />
     <!-- no translation found for ffx_vintage (8348759951363844780) -->
@@ -312,15 +320,426 @@
     <skip />
     <!-- no translation found for switch_photo_grid (3681299459107925725) -->
     <skip />
+    <!-- no translation found for switch_photo_fullscreen (8360489096099127071) -->
+    <skip />
     <!-- no translation found for trimming (9122385768369143997) -->
     <skip />
+    <!-- no translation found for muting (5094925919589915324) -->
+    <skip />
     <!-- no translation found for please_wait (7296066089146487366) -->
     <skip />
-    <!-- no translation found for save_into (4960537214388766062) -->
+    <!-- no translation found for save_into (9155488424829609229) -->
     <skip />
     <!-- no translation found for trim_too_short (751593965620665326) -->
     <skip />
     <!-- no translation found for pano_progress_text (1586851614586678464) -->
     <skip />
     <string name="save" msgid="613976532235060516">"Memorisar"</string>
+    <!-- no translation found for ingest_scanning (1062957108473988971) -->
+    <!-- no translation found for ingest_scanning (2048262851775139720) -->
+    <skip />
+    <!-- no translation found for ingest_number_of_items_scanned:zero (2623289390474007396) -->
+    <!-- no translation found for ingest_number_of_items_scanned:one (4340019444460561648) -->
+    <!-- no translation found for ingest_number_of_items_scanned:other (3138021473860555499) -->
+    <!-- no translation found for ingest_sorting (1028652103472581918) -->
+    <!-- no translation found for ingest_sorting (624687230903648118) -->
+    <skip />
+    <!-- no translation found for ingest_scanning_done (8911916277034483430) -->
+    <skip />
+    <!-- no translation found for ingest_importing (7456633398378527611) -->
+    <skip />
+    <!-- no translation found for ingest_empty_device (2010470482779872622) -->
+    <skip />
+    <!-- no translation found for ingest_no_device (3054128223131382122) -->
+    <skip />
+    <string name="camera_error_title" msgid="6484667504938477337">"Errur da la camera"</string>
+    <!-- no translation found for cannot_connect_camera (955440687597185163) -->
+    <skip />
+    <!-- no translation found for camera_disabled (8923911090533439312) -->
+    <skip />
+    <string name="camera_label" msgid="6346560772074764302">"Camera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Camera da video"</string>
+    <string name="wait" msgid="8600187532323801552">"Spetgar..."</string>
+    <!-- no translation found for no_storage (7335975356349008814) -->
+    <skip />
+    <!-- no translation found for no_storage (5137703033746873624) -->
+    <skip />
+    <!-- no translation found for preparing_sd (6104019983528341353) -->
+    <skip />
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Preparar la carta SD..."</string>
+    <!-- no translation found for access_sd_fail (8147993984037859354) -->
+    <skip />
+    <!-- no translation found for access_sd_fail (1584968646870054352) -->
+    <skip />
+    <string name="review_cancel" msgid="8188009385853399254">"INTERRUMPER"</string>
+    <!-- no translation found for review_ok (1156261588693116433) -->
+    <skip />
+    <!-- no translation found for time_lapse_title (4360632427760662691) -->
+    <skip />
+    <!-- no translation found for pref_camera_id_title (4040791582294635851) -->
+    <skip />
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Enavos"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Frunt"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Memorisar la posiziun"</string>
+    <!-- no translation found for pref_camera_location_label (2254270920298609161) -->
+    <skip />
+    <!-- no translation found for pref_camera_timer_title (3105232208281893389) -->
+    <skip />
+    <!-- no translation found for pref_camera_timer_entry:one (1654523400981245448) -->
+    <!-- no translation found for pref_camera_timer_entry:other (6455381617076792481) -->
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <!-- no translation found for pref_camera_timer_sound_title (2469008631966169105) -->
+    <skip />
+    <!-- no translation found for setting_off (4480039384202951946) -->
+    <skip />
+    <!-- no translation found for setting_on (8602246224465348901) -->
+    <skip />
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Qualitad dal video"</string>
+    <!-- no translation found for pref_video_quality_entry_high (8664038216234805914) -->
+    <skip />
+    <!-- no translation found for pref_video_quality_entry_low (7258507152393173784) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_title (6245716906744079302) -->
+    <skip />
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Parameters da la camera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Parameters da la camera da video"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Grondezza dal maletg"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_13mp (675309554194481780) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_8mp (259953780932849079) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_5mp (2882928212030661159) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_3mp (741415860337400696) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_2mp (1753709802245460393) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_1_3mp (829109608140747258) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_1mp (1669725616780375066) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_vga (806934254162981919) -->
+    <skip />
+    <!-- no translation found for pref_camera_picturesize_entry_qvga (8576186463069770133) -->
+    <skip />
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Modus da focussar"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatic"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinit"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <!-- no translation found for pref_camera_focusmode_label_auto (8547956917516317183) -->
+    <skip />
+    <!-- no translation found for pref_camera_focusmode_label_infinity (4272904160062531778) -->
+    <skip />
+    <!-- no translation found for pref_camera_focusmode_label_macro (8749317592620908054) -->
+    <skip />
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Modus da chametg (flash)"</string>
+    <!-- no translation found for pref_camera_flashmode_label (7546741624882856171) -->
+    <skip />
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatic"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Activà"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Deactivà"</string>
+    <!-- no translation found for pref_camera_flashmode_label_auto (8854671890619026197) -->
+    <skip />
+    <!-- no translation found for pref_camera_flashmode_label_on (7347504762794840140) -->
+    <skip />
+    <!-- no translation found for pref_camera_flashmode_label_off (3541596735596053416) -->
+    <skip />
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Equiliber da l\'alv"</string>
+    <!-- no translation found for pref_camera_whitebalance_label (7467403405883190920) -->
+    <skip />
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatic"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Pair electric"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Glisch dal di"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Glisch da neon"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nivlus"</string>
+    <!-- no translation found for pref_camera_whitebalance_label_auto (1479694362310491429) -->
+    <skip />
+    <!-- no translation found for pref_camera_whitebalance_label_incandescent (7427628260209908900) -->
+    <skip />
+    <!-- no translation found for pref_camera_whitebalance_label_daylight (1859710806141461399) -->
+    <skip />
+    <!-- no translation found for pref_camera_whitebalance_label_fluorescent (5173251749161337707) -->
+    <skip />
+    <!-- no translation found for pref_camera_whitebalance_label_cloudy (8230173517179285320) -->
+    <skip />
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Modus da scena"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatic"</string>
+    <!-- no translation found for pref_camera_scenemode_entry_hdr (2923388802899511784) -->
+    <skip />
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acziun"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Notg"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Rendida dal sulegl"</string>
+    <!-- no translation found for pref_camera_scenemode_entry_party (907053529286788253) -->
+    <skip />
+    <!-- no translation found for pref_camera_scenemode_label_auto (4475096836397300237) -->
+    <skip />
+    <!-- no translation found for pref_camera_scenemode_label_action (964748409622151496) -->
+    <skip />
+    <!-- no translation found for pref_camera_scenemode_label_night (1269871886845854574) -->
+    <skip />
+    <!-- no translation found for pref_camera_scenemode_label_sunset (2802732082948866877) -->
+    <skip />
+    <!-- no translation found for pref_camera_scenemode_label_party (1409459091844374828) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label (7592784692450586126) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label_off (4987856883590176585) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label_one (1101814103087928898) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label_three (1047399297342955649) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label_ten (6274681535347260279) -->
+    <skip />
+    <!-- no translation found for pref_camera_countdown_label_fifteen (4544824246687597089) -->
+    <skip />
+    <!-- no translation found for not_selectable_in_scene_mode (2970291701448555126) -->
+    <skip />
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exposiziun"</string>
+    <!-- no translation found for pref_exposure_label (552624394642497940) -->
+    <skip />
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <!-- no translation found for pref_camera_hdr_label (7217211253357027510) -->
+    <skip />
+    <!-- no translation found for pref_camera_id_label_back (8745553500400332333) -->
+    <skip />
+    <!-- no translation found for pref_camera_id_label_front (8699439330056996709) -->
+    <skip />
+    <!-- no translation found for dialog_ok (6263301364153382152) -->
+    <skip />
+    <!-- no translation found for spaceIsLow_content (4401325203349203177) -->
+    <skip />
+    <!-- no translation found for spaceIsLow_content (1732882643101247179) -->
+    <skip />
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Cuntanschì la grondezza maximala."</string>
+    <!-- no translation found for pano_too_fast_prompt (2823839093291374709) -->
+    <skip />
+    <!-- no translation found for pano_dialog_prepare_preview (4788441554128083543) -->
+    <skip />
+    <!-- no translation found for pano_dialog_panorama_failed (2155692796549642116) -->
+    <skip />
+    <!-- no translation found for pano_dialog_title (5755531234434437697) -->
+    <skip />
+    <!-- no translation found for pano_capture_indication (8248825828264374507) -->
+    <skip />
+    <!-- no translation found for pano_dialog_waiting_previous (7800325815031423516) -->
+    <skip />
+    <!-- no translation found for pano_review_saving_indication_str (2054886016665130188) -->
+    <skip />
+    <!-- no translation found for pano_review_rendering (2887552964129301902) -->
+    <skip />
+    <!-- no translation found for tap_to_focus (8863427645591903760) -->
+    <skip />
+    <!-- no translation found for pref_video_effect_title (8243182968457289488) -->
+    <skip />
+    <!-- no translation found for effect_none (3601545724573307541) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_squeeze (1207235692524289171) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_big_eyes (3945182409691408412) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_big_mouth (7528748779754643144) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_small_mouth (3848209817806932565) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_big_nose (5180533098740577137) -->
+    <skip />
+    <!-- no translation found for effect_goofy_face_small_eyes (1070355596290331271) -->
+    <skip />
+    <!-- no translation found for effect_backdropper_space (7935661090723068402) -->
+    <skip />
+    <!-- no translation found for effect_backdropper_sunset (45198943771777870) -->
+    <skip />
+    <!-- no translation found for effect_backdropper_gallery (959158844620991906) -->
+    <skip />
+    <!-- no translation found for bg_replacement_message (9184270738916564608) -->
+    <skip />
+    <!-- no translation found for video_snapshot_hint (18833576851372483) -->
+    <skip />
+    <!-- no translation found for video_recording_started (4132915454417193503) -->
+    <skip />
+    <!-- no translation found for video_recording_stopped (5086919511555808580) -->
+    <skip />
+    <!-- no translation found for disable_video_snapshot_hint (4957723267826476079) -->
+    <skip />
+    <!-- no translation found for clear_effects (5485339175014139481) -->
+    <skip />
+    <!-- no translation found for effect_silly_faces (8107732405347155777) -->
+    <skip />
+    <!-- no translation found for effect_background (6579360207378171022) -->
+    <skip />
+    <!-- no translation found for accessibility_shutter_button (2664037763232556307) -->
+    <skip />
+    <!-- no translation found for accessibility_menu_button (7140794046259897328) -->
+    <skip />
+    <!-- no translation found for accessibility_review_thumbnail (8961275263537513017) -->
+    <skip />
+    <!-- no translation found for accessibility_camera_picker (8807945470215734566) -->
+    <skip />
+    <!-- no translation found for accessibility_mode_picker (3278002189966833100) -->
+    <skip />
+    <!-- no translation found for accessibility_second_level_indicators (3855951632917627620) -->
+    <skip />
+    <!-- no translation found for accessibility_back_to_first_level (5234411571109877131) -->
+    <skip />
+    <!-- no translation found for accessibility_zoom_control (1339909363226825709) -->
+    <skip />
+    <!-- no translation found for accessibility_decrement (1411194318538035666) -->
+    <skip />
+    <!-- no translation found for accessibility_increment (8447850530444401135) -->
+    <skip />
+    <!-- no translation found for accessibility_check_box (7317447218256584181) -->
+    <skip />
+    <!-- no translation found for accessibility_switch_to_camera (5951340774212969461) -->
+    <skip />
+    <!-- no translation found for accessibility_switch_to_video (4991396355234561505) -->
+    <skip />
+    <!-- no translation found for accessibility_switch_to_panorama (604756878371875836) -->
+    <skip />
+    <!-- no translation found for accessibility_switch_to_new_panorama (8116783308051524188) -->
+    <skip />
+    <!-- no translation found for accessibility_review_cancel (9070531914908644686) -->
+    <skip />
+    <!-- no translation found for accessibility_review_ok (7793302834271343168) -->
+    <skip />
+    <!-- no translation found for accessibility_review_retake (659300290054705484) -->
+    <skip />
+    <!-- no translation found for accessibility_play_video (7596298365794810207) -->
+    <skip />
+    <!-- no translation found for accessibility_pause_video (6526344477133046653) -->
+    <skip />
+    <!-- no translation found for accessibility_reload_video (3250335917598607232) -->
+    <skip />
+    <!-- no translation found for accessibility_time_bar (1414029843602604531) -->
+    <skip />
+    <!-- no translation found for capital_on (5491353494964003567) -->
+    <skip />
+    <!-- no translation found for capital_off (7231052688467970897) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_off (3490489191038309496) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_500 (2949719376111679816) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_1000 (1672458758823855874) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_1500 (3415071702490624802) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_2000 (827813989647794389) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_2500 (5750464143606788153) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_3000 (2664846627499751396) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_4000 (7303255804306382651) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_5000 (6800566761690741841) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_6000 (8545447466540319539) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_10000 (3105568489694909852) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_12000 (6055574367392821047) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_15000 (2656164845371833761) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_24000 (2192628967233421512) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_30000 (5923393773260634461) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_60000 (4678581247918524850) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_90000 (1187029705069674152) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_120000 (145301938098991278) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_150000 (793707078196731912) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_180000 (1785467676466542095) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_240000 (3734507766184666356) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_300000 (7442765761995328639) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_360000 (6724596937972563920) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_600000 (6563665954471001352) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_720000 (8969801372893266408) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_900000 (5803172407245902896) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_1440000 (6286246349698492186) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_1800000 (5042628461448570758) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_3600000 (6366071632666482636) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_5400000 (536117788694519019) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_7200000 (6846617415182608533) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_9000000 (4242839574025261419) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_10800000 (2766886102170605302) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_14400000 (7497934659667867582) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_18000000 (8783643014853837140) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_21600000 (5005078879234015432) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_36000000 (69942198321578519) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_43200000 (285992046818504906) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_54000000 (5740227373848829515) -->
+    <skip />
+    <!-- no translation found for pref_video_time_lapse_frame_interval_86400000 (9040201678470052298) -->
+    <skip />
+    <!-- no translation found for time_lapse_seconds (2105521458391118041) -->
+    <skip />
+    <!-- no translation found for time_lapse_minutes (7738520349259013762) -->
+    <skip />
+    <!-- no translation found for time_lapse_hours (1776453661704997476) -->
+    <skip />
+    <!-- no translation found for time_lapse_interval_set (2486386210951700943) -->
+    <skip />
+    <!-- no translation found for set_time_interval (2970567717633813771) -->
+    <skip />
+    <!-- no translation found for set_time_interval_help (6665849510484821483) -->
+    <skip />
+    <!-- no translation found for set_timer_help (5007708849404589472) -->
+    <skip />
+    <!-- no translation found for set_duration (5578035312407161304) -->
+    <skip />
+    <!-- no translation found for count_down_title_text (4976386810910453266) -->
+    <skip />
+    <!-- no translation found for remember_location_title (9060472929006917810) -->
+    <skip />
+    <!-- no translation found for remember_location_prompt (724592331305808098) -->
+    <skip />
+    <!-- no translation found for remember_location_no (7541394381714894896) -->
+    <skip />
+    <!-- no translation found for remember_location_yes (862884269285964180) -->
+    <skip />
+    <!-- no translation found for menu_camera (3476709832879398998) -->
+    <skip />
+    <!-- no translation found for menu_search (7580008232297437190) -->
+    <skip />
+    <!-- no translation found for tab_photos (9110813680630313419) -->
+    <skip />
+    <!-- no translation found for tab_albums (8079449907770685691) -->
+    <skip />
+    <!-- no translation found for camera_menu_more_label (6868642182125198710) -->
+    <skip />
+    <!-- no translation found for camera_menu_settings_label (875454962069404723) -->
+    <skip />
+    <!-- no translation found for number_of_photos:one (6949174783125614798) -->
+    <!-- no translation found for number_of_photos:other (3813306834113858135) -->
 </resources>
diff --git a/res/values-ro/filtershow_strings.xml b/res/values-ro/filtershow_strings.xml
index ff26d1f..3798d44 100644
--- a/res/values-ro/filtershow_strings.xml
+++ b/res/values-ro/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Nu se poate încărca imaginea!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Se setează imaginea de fundal"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Nu s-au putut descărca fotografiile. Rețea indisponibilă."</string>
     <string name="original" msgid="3524493791230430897">"Originală"</string>
     <string name="borders" msgid="2067345080568684614">"Chenar"</string>
-    <string name="done" msgid="3112344807927554662">"Terminat"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Anulaţi"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Repetaţi"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Afişaţi istoricul"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ascundeţi istoricul"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Afişaţi stare foto."</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ascundeţi stare foto"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Afișați efectele aplicate"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ascundeți efectele aplicate"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Setări"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Ați adus modificări imaginii pe care nu le-ați salvat."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Doriți să salvați înainte de a ieși?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Salvați și ieșiți"</string>
+    <string name="exit" msgid="242642957038770113">"Ieșiți"</string>
     <string name="history" msgid="455767361472692409">"Istoric"</string>
     <string name="reset" msgid="9013181350779592937">"Resetaţi"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Starea curentă a imaginii"</string>
+    <string name="imageState" msgid="8632586742752891968">"Efecte aplicate"</string>
     <string name="compare_original" msgid="8140838959007796977">"Comparaţi"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Aplicaţi"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Resetaţi"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Niciunul"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fix"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planetă mică"</string>
     <string name="exposure" msgid="6526397045949374905">"Expunere"</string>
     <string name="sharpness" msgid="6463103068318055412">"Claritate"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Culoare auto."</string>
     <string name="hue" msgid="6231252147971086030">"Tonalitate"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Umbre"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Puncte luminoz."</string>
     <string name="curvesRGB" msgid="915010781090477550">"Curbe"</string>
     <string name="vignette" msgid="934721068851885390">"Vignetare"</string>
     <string name="redeye" msgid="4508883127049472069">"Ochi roşii"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Desenați"</string>
     <string name="straighten" msgid="26025591664983528">"Îndreptare"</string>
     <string name="crop" msgid="5781263790107850771">"Decupare"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotire"</string>
     <string name="mirror" msgid="5482518108154883096">"Oglindă"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Niciunul"</string>
+    <string name="edge" msgid="7036064886242147551">"Margini"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Mostră"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Roşu"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Verde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Albastru"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Dimensiune"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Culoare"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linii"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Stropire"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Ștergeți"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Alegeți culoarea personalizată"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Selectați culoarea"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Selectați dimensiunea"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Rezultat"</string>
 </resources>
diff --git a/res/values-ro/photoeditor_strings.xml b/res/values-ro/photoeditor_strings.xml
deleted file mode 100644
index 1c6b721..0000000
--- a/res/values-ro/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Studio foto"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotografia nu a putut fi încărcată"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Fotografia editată nu a putut fi salvată"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Fotografia editată a fost salvată în <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Renunţaţi la modificările nesalvate?"</string>
-    <string name="yes" msgid="5402582493291792293">"Da"</string>
-    <string name="save" msgid="5516670392524294967">"SALVAŢI"</string>
-    <string name="autofix" msgid="1663414996270538748">"Remediere auto."</string>
-    <string name="crop" msgid="7598378507763334041">"Decupaţi"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Suprapunere"</string>
-    <string name="documentary" msgid="50396326708699797">"Documentar"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Două culori"</string>
-    <string name="facelift" msgid="6205748523156172637">"Îmbujorare faţă"</string>
-    <string name="facetan" msgid="4412831806626044111">"Bronzare faţă"</string>
-    <string name="filllight" msgid="2644989991700022526">"Lumină complet."</string>
-    <string name="fisheye" msgid="6037488646928998921">"Ochi de peşte"</string>
-    <string name="flip" msgid="2357692401826287480">"Răsturnare"</string>
-    <string name="grain" msgid="7487585304579789098">"Granulaţie film"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Alb-negru"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Alb-negru"</string>
-    <string name="highlight" msgid="3902653944386623972">"Evidenţieri"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizare"</string>
-    <string name="redeye" msgid="4958448806369928239">"Ochi roşii"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotaţie"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturaţie"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Umbre"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Clarificare"</string>
-    <string name="straighten" msgid="5217801513491493491">"Îndreptare"</string>
-    <string name="temperature" msgid="1607987938521534517">"Căldură"</string>
-    <string name="tint" msgid="154435943863418434">"Nuanţă"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignetare"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Trageţi marcatorii pentru a decupa fotografia"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Desenaţi pe fotografie pentru a crea doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Trageţi o fotografie pentru a o răsturna"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Apăsaţi pe ochii roşii pentru eliminare"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Trageţi fotografia pentru a o roti"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Trageţi de fotografie pentru a o îndrepta"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efecte de expunere"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Efecte de culoare"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Efecte artistice"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Remediaţi"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Anulaţi"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Repetaţi"</string>
-</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 87df215..7d10cfc 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -49,17 +49,17 @@
     <string name="set_wallpaper" msgid="8491121226190175017">"Setaţi imag. fundal"</string>
     <string name="wallpaper" msgid="140165383777262070">"Se setează imaginea de fundal..."</string>
     <string name="camera_setas_wallpaper" msgid="797463183863414289">"Imagine de fundal"</string>
-    <string name="delete" msgid="2839695998251824487">"Ştergeţi"</string>
+    <string name="delete" msgid="2839695998251824487">"Ștergeţi"</string>
   <plurals name="delete_selection">
-    <item quantity="one" msgid="6453379735401083732">"Ştergeţi articolul selectat?"</item>
-    <item quantity="other" msgid="5874316486520635333">"Ştergeţi articolele selectate?"</item>
+    <item quantity="one" msgid="6453379735401083732">"Ștergeţi articolul selectat?"</item>
+    <item quantity="other" msgid="5874316486520635333">"Ștergeţi articolele selectate?"</item>
   </plurals>
     <string name="confirm" msgid="8646870096527848520">"Confirmaţi"</string>
     <string name="cancel" msgid="3637516880917356226">"Anulaţi"</string>
     <string name="share" msgid="3619042788254195341">"Distribuiţi"</string>
     <string name="share_panorama" msgid="2569029972820978718">"Trimiteți panorama"</string>
     <string name="share_as_photo" msgid="8959225188897026149">"Trimiteți fotografia"</string>
-    <string name="deleted" msgid="6795433049119073871">"Ştearsă"</string>
+    <string name="deleted" msgid="6795433049119073871">"Ștearsă"</string>
     <string name="undo" msgid="2930873956446586313">"ANULAŢI"</string>
     <string name="select_all" msgid="3403283025220282175">"Selectaţi-le pe toate"</string>
     <string name="deselect_all" msgid="5758897506061723684">"Deselectaţi-le pe toate"</string>
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rotiţi spre dreapta"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Elementul nu a putut fi găsit."</string>
     <string name="edit" msgid="1502273844748580847">"Editaţi"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Editare simplă"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Se proces. solicit. de stocare în memoria cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Mem. cache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Decupaţi"</string>
     <string name="trim_action" msgid="703098114452883524">"Decupaţi"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Dezact. sunetul"</string>
     <string name="set_as" msgid="3636764710790507868">"Setaţi ca"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Nu se poate dezactiva sunetul."</string>
     <string name="video_err" msgid="7003051631792271009">"Nu se poate reda videoclipul."</string>
     <string name="group_by_location" msgid="316641628989023253">"După locaţie"</string>
     <string name="group_by_time" msgid="9046168567717963573">"După dată"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Bliţ activat"</string>
     <string name="flash_off" msgid="1445443413822680010">"Fără bliţ"</string>
+    <string name="unknown" msgid="3506693015896912952">"Necunoscută"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Nu este disponibilă nicio stocare externă"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Afişare tip bandă de film"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Afişare tip grilă"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Ecran complet"</string>
     <string name="trimming" msgid="9122385768369143997">"Se ajustează"</string>
+    <string name="muting" msgid="5094925919589915324">"Se dezact. sunetul"</string>
     <string name="please_wait" msgid="7296066089146487366">"Aşteptaţi"</string>
-    <string name="save_into" msgid="4960537214388766062">"Se salvează videoclipul ajustat în album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Se salvează videoclipul în <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nu se poate ajusta: videoclipul ţintă este prea scurt"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Se redă panorama"</string>
     <string name="save" msgid="613976532235060516">"Salvaţi"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Se scanează conținutul..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d elemente scanate"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d element scanat"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d (de) elemente scanate"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Se sortează..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Scanare finalizată"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Se importă..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nu există conținut disponibil de importat pe acest gadget."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nu există niciun gadget MTP conectat"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Eroare cameră foto"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nu se poate conecta la camera foto."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Camera foto a fost dezactivată din cauza politicilor de securitate."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Cameră foto"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Cameră video"</string>
+    <string name="wait" msgid="8600187532323801552">"Aşteptaţi…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Înainte de a utiliza camera foto, montaţi dispozitivul de stocare USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Introduceţi un card SD înainte de a utiliza camera foto."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Se pregăteşte stocarea USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Se pregăteşte cardul SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nu s-a putut accesa stocarea USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nu s-a putut accesa cardul SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"Anulaţi"</string>
+    <string name="review_ok" msgid="1156261588693116433">"TERMINAT"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Înregistrare cu filmare lentă"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Alegeţi camera foto"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Înapoi"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Frontal"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Stocaţi locaţia"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOCAȚIE"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Cronometru numărătoare inversă"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"O secundă"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d (de) secunde"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Beep numărătoare"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Dezactivată"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Activată"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Calitate video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Ridicată"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Redusă"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Filmare lentă"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Setările camerei foto"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Setările camerei video"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Dimensiune fotografie"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapixeli"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixeli"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 MP"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 MP"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 MP"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 MP"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 MP"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Mod focus"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automat"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Infinit"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTOMAT"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINIT"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Mod flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"MOD BLIȚ"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automat"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Activat"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Dezactivată"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"BLIȚ AUTOMAT"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLIȚ ACTIVAT"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLIȚ DEZACTIVAT"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Balanţă de alb"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BALANS DE ALB"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automat"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Incandescent"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Lumină de zi"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescent"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Înnorat"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTOMAT"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENT"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"LUMINĂ DE ZI"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENT"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ÎNNORAT"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Mod Scenă"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automat"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Acţiune"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noapte"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Apus de soare"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Petrecere"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"NICIUNA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACȚIUNE"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOAPTE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"APUS DE SOARE"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PETRECERE"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"CRONOMETRU NUMĂRARE INVERSĂ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TEMPORIZATOR DEZACTIVAT"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"O SECUNDĂ"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SECUNDE"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SECUNDE"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SECUNDE"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Nu este selectabil în modul scenă."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Expunere"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPUNERE"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CAMERA FOTO FRONTALĂ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CAMERA FOTO SPATE"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Spaţiul de stocare USB este aproape ocupat. Editaţi setarea de calitate sau ştergeţi câteva imagini ori alte fişiere."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Cardul SD rămâne fără spaţiu de stocare. Editaţi setarea de calitate sau ştergeţi câteva imagini sau alte fişiere."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Dimensiune limită depăşită."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Prea repede"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Se pregăteşte panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorama nu a putut fi salvată."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoramă"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Se capturează panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Se aşteaptă panorama anterioară"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Se salvează.."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Se redă panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Atingeţi pentru a focaliza."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efecte"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Niciunul"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Comprimare"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Ochi mari"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Gură mare"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Gură mică"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Nas mare"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Ochi mici"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"În spaţiu"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Apus de soare"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Videoclip"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Aşezaţi dispozitivul jos."\n"Ieşiţi din cadru un moment."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Atingeţi pentru a fotografia în timpul înregistrării."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"A început înregistrarea videoclipului."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Înregistrarea videoclipului s-a oprit."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Instant. video este dezact. când efectele speciale sunt pornite."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Ștergeţi efectul"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"FEŢE PROSTUŢE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"FUNDAL"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Butonul Declanşaţi"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Butonul Meniu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Cea mai recentă fotografie"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Comutator pentru camera foto din faţă şi din spate"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Selector pentru modurile cameră foto, cameră video sau panoramă"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Mai multe comenzi pentru setări"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Închideţi comenzile pentru setări"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Comandă mărire/micşorare"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Reduceţi %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Măriţi %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Caseta de selectare %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Comutaţi la camera foto"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Comutaţi la camera video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Comutaţi la panoramă"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Comutaţi la o panoramă nouă"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Anulaţi examinarea"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Terminaţi examinarea"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Refaceţi"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Redați videoclipul"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Întrerupeți videoclipul"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Reîncărcați videoclipul"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Bara temporală a playerului video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ACTIVAT"</string>
+    <string name="capital_off" msgid="7231052688467970897">"DEZACTIVAT"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Dezactivat"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 secundă"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 de secunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 de minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 oră"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ore"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 de ore"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"secunde"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minute"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ore"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Terminat"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Setaţi intervalul de timp"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funcţia Filmare lentă este dezactivată. Activaţi-o pentru a seta intervalul de timp."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Cronometrul numărătorii inverse a fost dezactivat. Activați-l înainte de a fotografia."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Setați durata în secunde"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Numărătoare inversă până la fotografiere"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Doriţi să vă amintiţi locaţiile fotografiilor?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Etichetaţi-vă fotografiile şi videoclipurile cu locaţiile în care acestea au fost create."\n\n"Alte aplicaţii pot accesa aceste informaţii, împreună cu imaginile salvate."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nu, mulţumesc"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Cameră foto"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Căutați"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotografii"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albume"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"MAI MULTE OPȚIUNI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"SETĂRI"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotografie"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotografii"</item>
+  </plurals>
 </resources>
diff --git a/res/values-ru/filtershow_strings.xml b/res/values-ru/filtershow_strings.xml
index 215dffb..9e8bc13 100644
--- a/res/values-ru/filtershow_strings.xml
+++ b/res/values-ru/filtershow_strings.xml
@@ -20,23 +20,26 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Не удалось загрузить изображение."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Установка обоев…"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Не удалось скачать фото: сеть недоступна."</string>
     <string name="original" msgid="3524493791230430897">"Оригинал"</string>
-    <string name="borders" msgid="2067345080568684614">"Границы"</string>
-    <string name="done" msgid="3112344807927554662">"Готово"</string>
+    <string name="borders" msgid="2067345080568684614">"Рамка"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Отмена"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Повторить"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Показать историю"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Скрыть историю"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Показать состояние"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Скрыть состояние"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Отобразить эффекты"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Скрыть эффекты"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Настройки"</string>
+    <string name="unsaved" msgid="8704442449002374375">"У вас есть несохраненные изменения."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Сохранить изменения?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Сохранить и закрыть"</string>
+    <string name="exit" msgid="242642957038770113">"Закрыть"</string>
     <string name="history" msgid="455767361472692409">"История"</string>
     <string name="reset" msgid="9013181350779592937">"Сброс"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Состояние изображения"</string>
+    <string name="imageState" msgid="8632586742752891968">"Эффекты"</string>
     <string name="compare_original" msgid="8140838959007796977">"Сравнить"</string>
-    <string name="apply_effect" msgid="1218288221200568947">"Применить:"</string>
+    <string name="apply_effect" msgid="1218288221200568947"></string>
     <string name="reset_effect" msgid="7712605581024929564">"Сброс"</string>
     <string name="aspect" msgid="4025244950820813059">"Формат"</string>
     <string name="aspect1to1_effect" msgid="1159104543795779123">"1:1"</string>
@@ -46,29 +49,49 @@
     <string name="aspect5to7_effect" msgid="5122395569059384741">"5:7"</string>
     <string name="aspect7to5_effect" msgid="5780001758108328143">"7:5"</string>
     <string name="aspect9to16_effect" msgid="7740468012919660728">"16:9"</string>
-    <string name="aspectNone_effect" msgid="6263330561046574134">"Оригинал"</string>
+    <string name="aspectNone_effect" msgid="6263330561046574134">"Вручную"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Постоянное"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Закругление"</string>
     <string name="exposure" msgid="6526397045949374905">"Экспозиция"</string>
     <string name="sharpness" msgid="6463103068318055412">"Резкость"</string>
     <string name="contrast" msgid="2310908487756769019">"Контраст"</string>
-    <string name="vibrance" msgid="3326744578577835915">"Вибрация"</string>
-    <string name="saturation" msgid="7026791551032438585">"Насыщ-сть"</string>
+    <string name="vibrance" msgid="3326744578577835915">"Vibrance"</string>
+    <string name="saturation" msgid="7026791551032438585">"Насыщенность"</string>
     <string name="bwfilter" msgid="8927492494576933793">"Ч/Б"</string>
     <string name="wbalance" msgid="6346581563387083613">"Авторежим"</string>
     <string name="hue" msgid="6231252147971086030">"Оттенок"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Тени"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Блики"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Кривые"</string>
-    <string name="vignette" msgid="934721068851885390">"Виньет-ние"</string>
+    <string name="vignette" msgid="934721068851885390">"Виньетирование"</string>
     <string name="redeye" msgid="4508883127049472069">"Красные глаза"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Подмалевок"</string>
     <string name="straighten" msgid="26025591664983528">"Выровнять"</string>
-    <string name="crop" msgid="5781263790107850771">"Кадрирование"</string>
+    <string name="crop" msgid="5781263790107850771">"Кадрировать"</string>
     <string name="rotate" msgid="2796802553793795371">"Повернуть"</string>
     <string name="mirror" msgid="5482518108154883096">"Отразить"</string>
+    <string name="negative" msgid="6998313764388022201">"Негатив"</string>
     <string name="none" msgid="6633966646410296520">"Оригинал"</string>
+    <string name="edge" msgid="7036064886242147551">"Края"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Уорхол"</string>
+    <string name="downsample" msgid="3552938534146980104">"Уменьшить"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Красный"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Зеленый"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Синий"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Стиль"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Размер"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Цвет"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Линии"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Маркер"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Распыление"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Очистить"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Выбрать свой цвет"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Выберите цвет"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Выберите размер"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"ОК"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Как в оригинале"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Результат"</string>
 </resources>
diff --git a/res/values-ru/photoeditor_strings.xml b/res/values-ru/photoeditor_strings.xml
deleted file mode 100644
index a143f1b..0000000
--- a/res/values-ru/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Фотостудия"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Не удалось загрузить фотографию"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Не удалось сохранить изменения"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Измененная фотография сохранена в папке <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Отменить несохраненные изменения?"</string>
-    <string name="yes" msgid="5402582493291792293">"Да"</string>
-    <string name="save" msgid="5516670392524294967">"ГОТОВО"</string>
-    <string name="autofix" msgid="1663414996270538748">"Авто"</string>
-    <string name="crop" msgid="7598378507763334041">"Кадрировать"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Кроссобработка"</string>
-    <string name="documentary" msgid="50396326708699797">"Документальные"</string>
-    <string name="doodle" msgid="1686409894518940990">"Подмалевок"</string>
-    <string name="duotone" msgid="8145893940788467106">"Двойной тон"</string>
-    <string name="facelift" msgid="6205748523156172637">"Сияние"</string>
-    <string name="facetan" msgid="4412831806626044111">"Загар"</string>
-    <string name="filllight" msgid="2644989991700022526">"Осветление"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Рыбий глаз"</string>
-    <string name="flip" msgid="2357692401826287480">"Отразить"</string>
-    <string name="grain" msgid="7487585304579789098">"Зернистость"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Ч/Б"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Черно-белые"</string>
-    <string name="highlight" msgid="3902653944386623972">"Блики"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Ломо"</string>
-    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
-    <string name="posterize" msgid="4139212359561383385">"Постеризация"</string>
-    <string name="redeye" msgid="4958448806369928239">"Красные глаза"</string>
-    <string name="rotate" msgid="6607597269792373083">"Поворот"</string>
-    <string name="saturation" msgid="8621322012271169931">"Насыщенность"</string>
-    <string name="sepia" msgid="7978093531824705601">"Сепия"</string>
-    <string name="shadow" msgid="8235188588101973090">"Затенение"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Резкость"</string>
-    <string name="straighten" msgid="5217801513491493491">"Выравнивание"</string>
-    <string name="temperature" msgid="1607987938521534517">"Тепло"</string>
-    <string name="tint" msgid="154435943863418434">"Оттенок"</string>
-    <string name="vignette" msgid="7648125924662648282">"Виньетирование"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Чтобы кадрировать фото, перетащите маркеры"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Рисуйте на фото пальцем"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Чтобы отразить фото, перетащите его край"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Нажмите на красные глаза, чтобы убрать их"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Чтобы повернуть фото, перетащите его край"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Чтобы выровнять фото, перетащите его"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Экспозиция"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Цветовые эффекты"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Спецэффекты"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Изменить"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Отменить"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Повторить"</string>
-</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index d55f6dc..6234d00 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Повернуть вправо"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Элемент не найден."</string>
     <string name="edit" msgid="1502273844748580847">"Изменить"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Простая правка"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Обработка запросов на кэширование"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кэширование..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Кадрировать"</string>
     <string name="trim_action" msgid="703098114452883524">"Обрезка"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Отключить звук"</string>
     <string name="set_as" msgid="3636764710790507868">"Установить как"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Произошла ошибка"</string>
     <string name="video_err" msgid="7003051631792271009">"Не удалось воспроизвести видео."</string>
     <string name="group_by_location" msgid="316641628989023253">"По месту съемки"</string>
     <string name="group_by_time" msgid="9046168567717963573">"По времени создания"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Авто"</string>
     <string name="flash_on" msgid="7891556231891837284">"Со вспышкой"</string>
     <string name="flash_off" msgid="1445443413822680010">"Без вспышки"</string>
+    <string name="unknown" msgid="3506693015896912952">"Неизвестно"</string>
     <string name="ffx_original" msgid="372686331501281474">"Оригинал"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Винтаж"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Полароид"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Фотоавтомат"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Отбеливание"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Синева"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"Ч/Б"</string>
@@ -191,12 +195,245 @@
     <string name="help" msgid="7368960711153618354">"Справка"</string>
     <string name="no_external_storage_title" msgid="2408933644249734569">"Накопитель не найден"</string>
     <string name="no_external_storage" msgid="95726173164068417">"Нет внешних накопителей"</string>
-    <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Кинолента"</string>
+    <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Лента"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Сетка"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Во весь экран"</string>
     <string name="trimming" msgid="9122385768369143997">"Обрезка"</string>
+    <string name="muting" msgid="5094925919589915324">"Подождите…"</string>
     <string name="please_wait" msgid="7296066089146487366">"Подождите…"</string>
-    <string name="save_into" msgid="4960537214388766062">"Сохраните видео в альбом:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Сохранение в альбом \"<xliff:g id="ALBUM_NAME">%1$s</xliff:g>\"…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Нельзя обрезать видео: оно слишком короткое"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Создание панорамы…"</string>
     <string name="save" msgid="613976532235060516">"Сохранить"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Сканирование…"</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Отсканировано: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"Отсканировано: %1$d"</item>
+    <item quantity="other" msgid="3138021473860555499">"Отсканировано: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Сортировка…"</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Сканирование завершено"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Импорт…"</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Ничего не найдено"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"MTP-устройство не подключено"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Ошибка камеры"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Не удалось подключиться к камере."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Камера отключена в соответствии с политикой безопасности."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Камера"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Видеокамера"</string>
+    <string name="wait" msgid="8600187532323801552">"Подождите..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Подключите USB-накопитель перед использованием камеры."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Вставьте SD-карту перед использованием камеры."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Подготовка USB-накопителя…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Подготовка карты SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Нет доступа к USB-накопителю."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Нет доступа к SD-карте."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ОТМЕНА"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ГОТОВО"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Режим замедленной съемки"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Выбор камеры"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Задняя"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Передняя"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Геотеги"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"МЕСТОПОЛОЖЕНИЕ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Автоспуск"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 сек."</item>
+    <item quantity="other" msgid="6455381617076792481">"%d сек."</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Звук таймера"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Выкл."</string>
+    <string name="setting_on" msgid="8602246224465348901">"Вкл."</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Качество видео"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Высокое качество"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Низкое качество"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Замедленная съемка"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Настройки камеры"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Настройки видеокамеры"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Размер фото"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 мегапикс."</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Мпикс. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 Мпикс."</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"640 х 480 пикс."</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"320 x 240 пикс."</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Режим фокусировки"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Авто"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Бесконечность"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Макро"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"АВТО"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"БЕСКОНЕЧНОСТЬ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"МАКРОСЪЕМКА"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Режим вспышки"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"РЕЖИМ ВСПЫШКИ"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Авто"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Вкл."</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Выкл."</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"АВТО"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"ВКЛ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"ВЫКЛ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Баланс белого"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"БАЛАНС БЕЛОГО"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Авто"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Лампа накаливания"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Солнечный свет"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Лампа дн. света"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Пасмурный день"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"АВТО"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ЛАМПА НАКАЛИВАНИЯ"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ДНЕВНОЙ СВЕТ"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ЛАМПА ДНЕВНОГО СВЕТА"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ПАСМУРНО"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Режим съемки"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Авто"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"Эффект HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Спорт"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ночь"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Закат"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Вечеринка"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"НЕТ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"РАЗМЫТИЕ В ДВИЖЕНИИ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"НОЧЬ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ЗАКАТ"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ВЕЧЕРИНКА"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"АВТОСПУСК"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"АВТОСПУСК ОТКЛЮЧЕН"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 СЕКУНДА"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 СЕКУНДЫ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 СЕКУНД"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 СЕКУНД"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Недоступно в режиме съемки"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Экспозиция"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ЭКСПОЗИЦИЯ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ФРОНТАЛЬНАЯ КАМЕРА"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ОСНОВНАЯ КАМЕРА"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"ОК"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Место на USB-накопителе заканчивается. Измените настройки качества или удалите ненужные изображения и другие файлы."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Место на вашей SD-карте заканчивается. Измените настройки качества или удалите ненужные изображения и другие файлы."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Достигнут предельный размер видео."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Очень быстро"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Обработка..."</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Не удалось сохранить файл."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Панорама"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Создание панорамы..."</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Обработка предыдущего файла…"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Сохранение…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Создание панорамы…"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Нажмите для фокусировки."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Эффекты"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Оригинал"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Узкое лицо"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Большие глаза"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Большой рот"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Маленький рот"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Большой нос"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Глаза в точку"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Космос"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Закат"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Мои видео"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Установите устройство на твердой поверхности."\n"Затем отойдите, чтобы вас было видно в камеру."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Нажмите, чтобы сделать фотографию во время записи."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Запись начата"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Запись остановлена"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Невозможно сделать снимок при включенных спецэффектах."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Убрать эффекты"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"СМЕШНЫЕ РОЖИЦЫ"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ФОН"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Кнопка \"Затвор\""</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Кнопка \"Меню\""</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Недавние фото"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Переключение на переднюю или заднюю камеру"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Переключатель между режимами \"Фото\", \"Видео\" и \"Панорама\""</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Дополнительные настройки"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Закрыть"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Зум"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Уменьшить %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Увеличить %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Флажок \"%1$s\""</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Режим \"Фото\""</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Режим \"Видео\""</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Режим \"Панорама\""</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Создать панораму"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Отмена"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Готово"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Ещё раз"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Воспроизвести видео"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Пауза"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Перезагрузить видео"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Полоса прокрутки видеоплеера"</string>
+    <string name="capital_on" msgid="5491353494964003567">"I"</string>
+    <string name="capital_off" msgid="7231052688467970897">"O"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Выкл."</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 сек."</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 мин."</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ч."</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ч."</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"сек."</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"мин."</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ч."</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Готово"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Задайте интервал"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Сначала включите режим замедленной съемки."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Чтобы камера фотографировала с задержкой, включите эту функцию."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Время"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Обратный отсчет перед съемкой"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Сохранять место съемки?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Информация о месте съемки будет автоматически добавляться в описание ваших фотографий и видеозаписей."\n\n"Доступ к этим данным, а также к самим фотографиям и видео смогут получить и другие приложения."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Нет, спасибо"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Поиск"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Фото"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Альбомы"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ЕЩЁ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"НАСТРОЙКИ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d фото"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d фото"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sk/filtershow_strings.xml b/res/values-sk/filtershow_strings.xml
index db4775b..9bb7648 100644
--- a/res/values-sk/filtershow_strings.xml
+++ b/res/values-sk/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Obrázok sa nepodarilo načítať!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Prebieha nastavovanie tapety"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotografiu sa nepodarilo prevziať. Sieť nie je k dispozícii."</string>
     <string name="original" msgid="3524493791230430897">"Pôvodné"</string>
     <string name="borders" msgid="2067345080568684614">"Okraje"</string>
-    <string name="done" msgid="3112344807927554662">"Hotovo"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Späť"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Znova"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Zobraziť históriu"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Skryť históriu"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Zobraz. stav obrázka"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Skryť stav obrázka"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Zobraziť použité efekty"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Skryť použité efekty"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Nastavenia"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Niektoré zmeny obrázka nie sú uložené."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Chcete zmeny pred ukončením uložiť?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Uložiť a ukončiť"</string>
+    <string name="exit" msgid="242642957038770113">"Ukončiť"</string>
     <string name="history" msgid="455767361472692409">"História"</string>
     <string name="reset" msgid="9013181350779592937">"Obnoviť"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Aktuálny stav obrázka"</string>
+    <string name="imageState" msgid="8632586742752891968">"Použité efekty"</string>
     <string name="compare_original" msgid="8140838959007796977">"Porovnať"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Použiť"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Obnoviť"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Žiadny"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Pevné"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Malá planéta"</string>
     <string name="exposure" msgid="6526397045949374905">"Expozícia"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ostrosť"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autom. farba"</string>
     <string name="hue" msgid="6231252147971086030">"Odtieň"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Tiene"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Najsvetlejšie tóny"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Krivky"</string>
     <string name="vignette" msgid="934721068851885390">"Vineta"</string>
     <string name="redeye" msgid="4508883127049472069">"Červené oči"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Kresliť"</string>
     <string name="straighten" msgid="26025591664983528">"Vyrovnať"</string>
     <string name="crop" msgid="5781263790107850771">"Orezanie"</string>
     <string name="rotate" msgid="2796802553793795371">"Otočiť"</string>
     <string name="mirror" msgid="5482518108154883096">"Zrkadliť"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatív"</string>
     <string name="none" msgid="6633966646410296520">"Žiadne"</string>
+    <string name="edge" msgid="7036064886242147551">"Hrany"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Zmen.vzor."</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Červená"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zelená"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Modrá"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Štýl"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Veľkosť"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Farba"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Čiary"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Zvýrazňovač"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Škvrna"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Vymazať"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Vybrať vlastnú farbu"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Vyberte farbu"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Vyberte veľkosť"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Pôvodné"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Výsledok"</string>
 </resources>
diff --git a/res/values-sk/photoeditor_strings.xml b/res/values-sk/photoeditor_strings.xml
deleted file mode 100644
index 55bb87f..0000000
--- a/res/values-sk/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotografiu sa nepodarilo načítať"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Upravenú fotografiu sa nepodarilo uložiť"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Uprav. fotografia bola ulož. do prieč. <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Zahodiť neuložené zmeny?"</string>
-    <string name="yes" msgid="5402582493291792293">"Áno"</string>
-    <string name="save" msgid="5516670392524294967">"ULOŽIŤ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Automat. oprava"</string>
-    <string name="crop" msgid="7598378507763334041">"Orezať"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Prelínanie"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokument"</string>
-    <string name="doodle" msgid="1686409894518940990">"Kreslenie"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dvojtónový efekt"</string>
-    <string name="facelift" msgid="6205748523156172637">"Žiara tváre"</string>
-    <string name="facetan" msgid="4412831806626044111">"Opálenie tváre"</string>
-    <string name="filllight" msgid="2644989991700022526">"Zad. osvetlenie"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Rybie oko"</string>
-    <string name="flip" msgid="2357692401826287480">"Prevrátenie"</string>
-    <string name="grain" msgid="7487585304579789098">"Zrnitý film"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Čiernobiele"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Čiernobiele"</string>
-    <string name="highlight" msgid="3902653944386623972">"Prisvietenie"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatív"</string>
-    <string name="posterize" msgid="4139212359561383385">"Posterizácia"</string>
-    <string name="redeye" msgid="4958448806369928239">"Červené oči"</string>
-    <string name="rotate" msgid="6607597269792373083">"Otočenie"</string>
-    <string name="saturation" msgid="8621322012271169931">"Sýtosť"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sépiové farby"</string>
-    <string name="shadow" msgid="8235188588101973090">"Tiene"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Zaostrenie"</string>
-    <string name="straighten" msgid="5217801513491493491">"Vyrovnanie"</string>
-    <string name="temperature" msgid="1607987938521534517">"Teplo"</string>
-    <string name="tint" msgid="154435943863418434">"Tónovanie"</string>
-    <string name="vignette" msgid="7648125924662648282">"Medailónik"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Potiahnutím značiek orežete fotografiu"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Na fotografii môžete kresliť prstom"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Potiahnutím prstom fotografiu prevrátite"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Červené oči odstránite klepnutím na ne"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Potiahnutím prstom fotografiu otočíte"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Potiahnutím prstom fotografiu vyrovnáte"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Efekty s expozíciou"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Farebné efekty"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Umelecké efekty"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Opraviť"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Späť"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Znova"</string>
-</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 6ae085e..fdb228f 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Otočiť doprava"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Položku sa nepodarilo nájsť."</string>
     <string name="edit" msgid="1502273844748580847">"Upraviť"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Jednoduchá úprava"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Spracovávanie žiadostí o ulož. do vyrovnáv. pamäte"</string>
     <string name="caching_label" msgid="4521059045896269095">"Do pamäte..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Orezať"</string>
     <string name="trim_action" msgid="703098114452883524">"Orezať"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Vypnúť zvuk"</string>
     <string name="set_as" msgid="3636764710790507868">"Použiť ako"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Zvuk videa sa nedá vypnúť."</string>
     <string name="video_err" msgid="7003051631792271009">"Video sa nepodarilo prehrať."</string>
     <string name="group_by_location" msgid="316641628989023253">"Podľa miesta"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Podľa času"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"S bleskom"</string>
     <string name="flash_off" msgid="1445443413822680010">"Bez blesku"</string>
+    <string name="unknown" msgid="3506693015896912952">"Neznáme"</string>
     <string name="ffx_original" msgid="372686331501281474">"Pôvodné"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Staré"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Okamžite"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Instantný"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Bielidlo"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Modrá"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"ČB"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"K dispozícii nie je žiadny externý ukladací priestor"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Zobrazenie filmového pásu"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Zobrazenie v mriežke"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Na celú obrazovku"</string>
     <string name="trimming" msgid="9122385768369143997">"Orezanie"</string>
+    <string name="muting" msgid="5094925919589915324">"Vypína sa zvuk..."</string>
     <string name="please_wait" msgid="7296066089146487366">"Počkajte"</string>
-    <string name="save_into" msgid="4960537214388766062">"Prebieha ukladanie orezaného videa do albumu:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Video sa ukladá do albumu <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Nie je možné orezať: výsledné video je príliš krátke"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Vykresľovanie panorámy"</string>
     <string name="save" msgid="613976532235060516">"Uložiť"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Prebieha vyhľadávanie obsahu..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"počet vyhľadaných položiek: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d vyhľadaná položka"</item>
+    <item quantity="other" msgid="3138021473860555499">"počet vyhľadaných položiek: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Prebieha zoradenie..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Vyhľadávanie bolo dokončené"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Prebieha import..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Nie je k dispozícii žiadny obsah na import do tohto zariadenia."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Nie je pripojené žiadne zariadenie MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Chyba fotoaparátu"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Nedá sa pripojiť k fotoaparátu."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Fotoaparát je zakázaný z dôvodu bezpečnostných pravidiel."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotoaparát"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Čakajte, prosím..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Pred použitím fotoaparátu pripojte zdieľané úložisko USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Pred použitím fotoaparátu vložte kartu SD."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Príprava uklad. priestoru USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Príprava karty SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Nepodarilo sa získať prístup k ukladaciemu priestoru USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Nepodarilo sa získať prístup ku karte SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ZRUŠIŤ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"HOTOVO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Časozberný záznam"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Vybrať fotoaparát"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Zozadu"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Spredu"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Úložné miesto"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"POLOHA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Časovač odpočítavania"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 s"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d s"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Zvuk pri odpočítavaní"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Vypnuté"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Zapnuté"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kvalita videa"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Vysoká"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Nízka"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Časozberné video"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Nastavenia fotoaparátu"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Nastavenie videokamery"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Veľkosť fotografie"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 Mpx"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixelov"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixlov"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 MP"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixle"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixle"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 MP (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixlov"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Režim zaostrenia"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Nekonečno"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"NEKONEČNO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Režim blesku"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"REŽIM BLESKU"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Auto"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Zapnuté"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Vypnuté"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATICKÝ BLESK"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLESK ZAPNUTÝ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLESK VYPNUTÝ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Vyváženie bielej"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"VYVÁŽENIE BIELEJ"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Auto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Žiariace"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Denné svetlo"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Svetielkujúce"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Zamračené"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ŽIAROVKA"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DENNÉ SVETLO"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ŽIARIVKA"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ZAMRAČENÉ"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scénický režim"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Auto"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Akcia"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noc"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Západ slnka"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Strana"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ŽIADNE"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AKCIA"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NOC"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ZÁPAD SLNKA"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"VEČIEROK"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ČASOVAČ ODPOČÍTAVANIA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ČASOVAČ VYPNUTÝ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDA"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDY"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKÚND"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKÚND"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Nie je možné vybrať v scénickom režime."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Expozícia"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOZÍCIA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"PREDNÝ FOTOAPARÁT"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ZADNÝ FOTOAPARÁT"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"V ukladacom priestore USB je málo miesta. Zmeňte nastavenie kvality alebo odstráňte niektoré obrázky či iné súbory."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Na karte SD je málo miesta. Zmeňte nastavenie kvality alebo odstráňte niektoré obrázky či iné súbory."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Bolo dosiahnuté obmedzenie veľkosti."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Veľmi rýchlo"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Príprava panorámy"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorámu sa nepodarilo uložiť."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panoráma"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Snímanie panorámy"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Čaká sa na predchádzajúcu panorámu"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Ukladá sa..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Vykresľovanie panorámy"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Dotykom zaostríte."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efekty"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Žiadne"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Stlačiť"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Veľké oči"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Veľké ústa"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Malé ústa"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Veľký nos"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Malé oči"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Vo vesmíre"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Západ slnka"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Vaše video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Položte svoje zariadenie."\n"Vyjdite na chvíľu zo zorného poľa."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Dotykom môžete počas záznamu fotiť."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Záznam videa bol spustený."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Záznam videa bol zastavený."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Pri zapnutých špeciálnych efektoch je vytváranie snímok zakázané."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Vymazať efekty"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"BLÁZNIVÉ TVÁRE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"POZADIE"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Tlačidlo uzávierky"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Tlačidlo Menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Posledná fotografia"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Prepínač medzi prednou a zadnou kamerou"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Prepínač medzi panoramatickým režimom a režimami fotoaparátu a videokamery"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Ďalšie ovládacie prvky nastavenia"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Zavrieť ovládacie prvky nastavenia"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Ovládanie priblíženia/oddialenia"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Znížiť %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Zvýšiť %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Začiarkavacie políčko %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Prepnúť na fotoaparát"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Prepnúť do režimu videa"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Prepnúť na panorámu"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Prepnúť na novú panorámu"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Zrušiť"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Hotovo"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Nasnímať znova"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Prehrať video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pozastaviť video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Znova načítať video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Časový pruh prehrávača videí"</string>
+    <string name="capital_on" msgid="5491353494964003567">"ZAPNUTÉ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"VYPNUTÉ"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Vypnuté"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekundy"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekúnd"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minúta"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minúty"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minút"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 hodina"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 hodiny"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 hodín"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 hodín"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 hodín"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 hodín"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 hodín"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 hodín"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekundy"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minúty"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"hodiny"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Hotovo"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Nastaviť časový interval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funkcia časozberného videa je vypnutá. Zapnite ju a a nastavte časový interval."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Časovač odpočítavania je vypnutý. Ak chcete odpočítavať pred aktivovaním spúšte fotoaparátu, zapnite ho."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Nastavte dobu trvania v sekundách"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Odpočítavanie spúšte fotoaparátu"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Zapamätať si, kde boli fotografie vytvorené?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Označte pre fotografie a videá polohy, kde boli zaznamenané."\n\n"Ostatné aplikácie môžu pristupovať k týmto informáciám aj k vašim uloženým snímkam."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nie, ďakujem"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Áno"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotoaparát"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Vyhľadávanie"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotografie"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumy"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ĎALŠIE MOŽNOSTI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"NASTAVENIA"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotografia"</item>
+    <item quantity="other" msgid="3813306834113858135">"Fotografie: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sl/filtershow_strings.xml b/res/values-sl/filtershow_strings.xml
index 917eaaf..cc65259 100644
--- a/res/values-sl/filtershow_strings.xml
+++ b/res/values-sl/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Slike ni mogoče naložiti."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Nastavljanje ozadja"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotografije ni bilo mogoče prenesti. Omrežje ni na voljo."</string>
     <string name="original" msgid="3524493791230430897">"Izvirnik"</string>
     <string name="borders" msgid="2067345080568684614">"Obrobe"</string>
-    <string name="done" msgid="3112344807927554662">"Končano"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Razveljavi"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Uveljavi"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Pokaži zgodovino"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Skrij zgodovino"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Pokaži stanje slike"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Skrij stanje slike"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Pokaži uporabljene učinke"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Skrij uporabljene učinke"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Nastavitve"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Spremembe te slike niso shranjene."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Ali želite pred zapiranjem shraniti?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Shrani in zapri"</string>
+    <string name="exit" msgid="242642957038770113">"Izhod"</string>
     <string name="history" msgid="455767361472692409">"Zgodovina"</string>
     <string name="reset" msgid="9013181350779592937">"Ponastavi"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Trenutno stanje slike"</string>
+    <string name="imageState" msgid="8632586742752891968">"Uporabljeni učinki"</string>
     <string name="compare_original" msgid="8140838959007796977">"Primerjaj"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Uporabi"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Ponastavi"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Brez"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Nespremenljivo"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Planetek"</string>
     <string name="exposure" msgid="6526397045949374905">"Osvetlitev"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ostrina"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Samodej. barva"</string>
     <string name="hue" msgid="6231252147971086030">"Odtenek"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Sence"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Svetli deli"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Krivulje"</string>
     <string name="vignette" msgid="934721068851885390">"Vinjeta"</string>
     <string name="redeye" msgid="4508883127049472069">"Rdeče oči"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Risanje"</string>
     <string name="straighten" msgid="26025591664983528">"Poravnanje"</string>
     <string name="crop" msgid="5781263790107850771">"Obrezovanje"</string>
     <string name="rotate" msgid="2796802553793795371">"Zavrti"</string>
     <string name="mirror" msgid="5482518108154883096">"Zrcaljenje"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Brez"</string>
+    <string name="edge" msgid="7036064886242147551">"Robovi"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Zmanjšanje velikosti"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Rdeče"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Zeleno"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Modro"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Slog"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Velikost"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Barva"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Črte"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Flomaster"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Packe"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Izbriši"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Izberite barvo po meri"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Izberite barvo"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Izberite velikost"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"V redu"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Izvirnik"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Rezultat"</string>
 </resources>
diff --git a/res/values-sl/photoeditor_strings.xml b/res/values-sl/photoeditor_strings.xml
deleted file mode 100644
index 4c82f91..0000000
--- a/res/values-sl/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotografije ni mogoče naložiti"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Urejene fotografije ni mogoče shraniti"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Urejena fotografija je shranjena v mapi <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Želite zavreči neshranjene spremembe?"</string>
-    <string name="yes" msgid="5402582493291792293">"Da"</string>
-    <string name="save" msgid="5516670392524294967">"SHRANI"</string>
-    <string name="autofix" msgid="1663414996270538748">"Samod. poprav."</string>
-    <string name="crop" msgid="7598378507763334041">"Obrezovanje"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Navzkr. obdel."</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentarno"</string>
-    <string name="doodle" msgid="1686409894518940990">"Čačka"</string>
-    <string name="duotone" msgid="8145893940788467106">"Dvotonsko"</string>
-    <string name="facelift" msgid="6205748523156172637">"Svetel obraz"</string>
-    <string name="facetan" msgid="4412831806626044111">"Zagorel obraz"</string>
-    <string name="filllight" msgid="2644989991700022526">"Dosvetlitev"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Ribje oko"</string>
-    <string name="flip" msgid="2357692401826287480">"Obrnjeno"</string>
-    <string name="grain" msgid="7487585304579789098">"Zrnatost filma"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Črno-belo"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Črno-belo"</string>
-    <string name="highlight" msgid="3902653944386623972">"Poudarki"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Poster"</string>
-    <string name="redeye" msgid="4958448806369928239">"Rdeče oči"</string>
-    <string name="rotate" msgid="6607597269792373083">"Zasukaj"</string>
-    <string name="saturation" msgid="8621322012271169931">"Nasičenost"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepija"</string>
-    <string name="shadow" msgid="8235188588101973090">"Sence"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Izostri"</string>
-    <string name="straighten" msgid="5217801513491493491">"Poravnavanje"</string>
-    <string name="temperature" msgid="1607987938521534517">"Toplota"</string>
-    <string name="tint" msgid="154435943863418434">"Obarvanje"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinjeta"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Povlecite oznake, da obrežete fotografijo"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Povlecite po fotografiji za prostoročno risanje"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Povlecite fotografijo, da jo obrnete"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Rdeče oči odstranite z dotikom"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Povlecite fotografijo, da jo zavrtite"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Povlecite fotografijo, da jo poravnate"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Osvetlitev"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Barvni učinki"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Umetniški učinki"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Popravek"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Razveljavi"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Uveljavi"</string>
-</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 7d13619..7343a47 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Zasukaj desno"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Ni mogoče najti elementa."</string>
     <string name="edit" msgid="1502273844748580847">"Urejanje"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Preprosto urejanje"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Obdelava zahtev za predpomnjenje"</string>
     <string name="caching_label" msgid="4521059045896269095">"Predpomnjenje ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Obrezovanje"</string>
     <string name="trim_action" msgid="703098114452883524">"Obreži"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Izklop zvoka"</string>
     <string name="set_as" msgid="3636764710790507868">"Nastavi kot"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Zvoka ni mogoče izklopiti."</string>
     <string name="video_err" msgid="7003051631792271009">"Videoposnetka ni mogoče predvajati."</string>
     <string name="group_by_location" msgid="316641628989023253">"Po lokaciji"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Po uri"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Samod."</string>
     <string name="flash_on" msgid="7891556231891837284">"Blis. sprožena"</string>
     <string name="flash_off" msgid="1445443413822680010">"Brez bliskav."</string>
+    <string name="unknown" msgid="3506693015896912952">"Neznana"</string>
     <string name="ffx_original" msgid="372686331501281474">"Izvirnik"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Starinsko"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Takoj"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Na voljo ni nobena zunanja naprava za shranjevanje"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Pogled filmskega traku"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Mrežni pogled"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Celozaslonski pogled"</string>
     <string name="trimming" msgid="9122385768369143997">"Obrezovanje"</string>
+    <string name="muting" msgid="5094925919589915324">"Izklop zvoka"</string>
     <string name="please_wait" msgid="7296066089146487366">"Počakajte"</string>
-    <string name="save_into" msgid="4960537214388766062">"Shranjevanje obrezanega videoposnetka v album:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Shranjevanje videa v album <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Obrezovanje ni mogoče: izvirni videoposnetek je prekratek"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Upodabljanje panorame"</string>
     <string name="save" msgid="613976532235060516">"Shrani"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Pregledovanje vsebine ..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Št. pregledanih elementov: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d pregledan element"</item>
+    <item quantity="other" msgid="3138021473860555499">"Št. pregledanih elementov: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Razvrščanje ..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Pregled končan"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Uvažanje ..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Ni na voljo vsebine za uvoz v to napravo."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Ni priključene naprave MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Napaka kamere"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Povezava s fotoaparatom ni mogoča."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Fotoaparat je onemogočen zaradi varnostnih pravilnikov."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Fotoaparat"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Kamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Počakajte ..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Pred uporabo fotoaparata vpnite pomnilnik USB."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Pred uporabo fotoaparata vstavite kartico SD."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Priprava pomnilnika USB ..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Priprava kartice SD ..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Do pomnilnika USB ni bilo mogoče dostopiti."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Do kartice SD ni bilo mogoče dostopiti."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"PREKLIČI"</string>
+    <string name="review_ok" msgid="1156261588693116433">"KONČANO"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Snemanje s časovnim zamikom"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Izberite fotoaparat"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Nazaj"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Pred"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Shrani lokacijo"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKACIJA"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Odštevalnik"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 s"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d s"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Zvok med odštevanjem"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Izklopljeno"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Vklopljeno"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kakovost videoposnetka"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Visoka"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Nizka"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Pospešena reprodukcija"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Nastavitve fotoaparata"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Nastavitve kamere"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Velikost slike"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 mio sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 milijonov slikovnih pik"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 M pik (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 M sl. pik"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Način ostrenja"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Samodejno"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Neskončno"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"SAMODEJNO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"NESKONČNO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Način bliskavice"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"NAČIN BLISKAVICE"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Samodejno"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Vključeno"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Izključeno"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"SAMODEJNA BLISKAVICA"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLISKAVICA VKLOPLJENA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"BLISKAVICA IZKLOPLJENA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Izravnava beline"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"NASTAVITEV BELINE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Samodejno"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Žareče"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dnevna svetloba"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescenčno"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Oblačno"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"SAMODEJNO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ŽAREČE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DNEVNA SVETLOBA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENČNO"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"OBLAČNO"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scenski način"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Samodejno"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Dejanje"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Noč"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Sončni zahod"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Stranka"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"BREZ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"DEJANJE"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"PONOČI"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SONČNI ZAHOD"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ZABAVA"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ODŠTEVALNIK"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"MERILNIK ČASA IZKLOPLJEN"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUNDA"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDE"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUND"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUND"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Ni mogoče izbrati v načinu prizora."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Osvetlitev"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"OSVETLITEV"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FOTOAPARAT SPREDAJ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"FOTOAPARAT ZADAJ"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"V redu"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Na vašem pomnilniku USB zmanjkuje prostora. Spremenite nastavitev kakovosti ali izbrišite nekaj slik ali drugih datotek."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Na kartici SD zmanjkuje prostora. Spremenite nastavitev kakovosti ali izbrišite nekaj slik ali drugih datotek."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Dosežena je omejitev velikosti."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Prehitro"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Priprava panorame"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorame ni bilo mogoče shraniti."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Snemanje panorame"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Čakanje na prejšnjo panoramo"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Shranjev. ..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Upodabljanje panorame"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Dotaknite se za ostrenje."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Učinki"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Brez"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Stiskanje"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Velike oči"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Velika usta"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Majhna usta"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Velik nos"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Majhne oči"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"V vesolju"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Sončni zahod"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Vaš video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Odložite napravo."\n"Za trenutek stopite iz vidnega polja."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Dotaknite se, če želite fotografirati med snemanjem."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Snemanje se je začelo."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Snemanje se je ustavilo."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Videoposnetek je onemogočen, ko so vklopljeni posebni učinki."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Počisti učinke"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"NORČAVI OBRAZI"</string>
+    <string name="effect_background" msgid="6579360207378171022">"OZADJE"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Gumb za fotografiranje"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Gumb za meni"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Najnovejša fotografija"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Preklop med fotoaparatom na sprednji in na hrbtni strani"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Izbirnik fotoaparata, videokamere ali panorame"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Več kontrolnikov nastavitev"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Zapri kontrolnike nastavitev"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Nadzor povečave/pomanjšave"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Pomanjšaj %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Povečaj %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Potrditveno polje %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Preklopi na fotoaparat"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Preklop na videoposnetek"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Preklop na panoramo"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Preklopi v novo panoramo"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Preklic pregleda"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Pregled opravljen"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Vnovični pregled posnetka"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Predvajaj videoposnetek"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Začasno ustavi videoposnetek"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Znova naloži videoposnetek"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Časovna vrstica videopredvajalnika"</string>
+    <string name="capital_on" msgid="5491353494964003567">"VKLOPLJENO"</string>
+    <string name="capital_off" msgid="7231052688467970897">"IZKLOPLJENO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Izklopljeno"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekunda"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekundi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekunde"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuta"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuti"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minute"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ura"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 uri"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ure"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ur"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ur"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ur"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ur"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ur"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ur"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunde"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minute"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ure"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Končano"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Nastavite časovni Interval"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Funkcija pospešene reprodukcije je izklopljena. Vklopite jo, da nastavite časovni interval."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Odštevalnik je izklopljen. Vklopite ga, če želite odštevanje pred fotografiranjem."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Nastavite trajanje v sekundah"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Odštevanje pred fotografiranjem"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Beleženje lokacije fotografije?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Fotografije in videoposnetke označite z lokacijami, na katerih so posneti."\n\n"Druge aplikacije lahko dostopajo do teh podatkov skupaj s shranjenimi slikami."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ne, hvala"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Da"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Fotoaparat"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Išči"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotografije"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albumi"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"VEČ MOŽNOSTI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"NASTAVITVE"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotografija"</item>
+    <item quantity="other" msgid="3813306834113858135">"Št. fotografij: %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sr/filtershow_strings.xml b/res/values-sr/filtershow_strings.xml
index 64721be..52f77b8 100644
--- a/res/values-sr/filtershow_strings.xml
+++ b/res/values-sr/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Није могуће учитати слику!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Подешавање позадине"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Није могуће преузети слику. Мрежа није доступна."</string>
     <string name="original" msgid="3524493791230430897">"Оригинална"</string>
     <string name="borders" msgid="2067345080568684614">"Ивице"</string>
-    <string name="done" msgid="3112344807927554662">"Готово"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Опозови"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Понови"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Прикажи историју"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Сакриј историју"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Прикажи статус слике"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Сакриј статус слике"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Прикажи примењене ефекте"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Сакриј примењене ефекте"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Подешавања"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Постоје несачуване измене ове слике."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Желите ли да сачувате пре него што изађете?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Сачувај и изађи"</string>
+    <string name="exit" msgid="242642957038770113">"Изађи"</string>
     <string name="history" msgid="455767361472692409">"Историја"</string>
     <string name="reset" msgid="9013181350779592937">"Поново постави"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Актуелни статус слике"</string>
+    <string name="imageState" msgid="8632586742752891968">"Примењени ефекти"</string>
     <string name="compare_original" msgid="8140838959007796977">"Упореди"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Примени"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Поново постави"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ниједно"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Фиксно"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Мала планета"</string>
     <string name="exposure" msgid="6526397045949374905">"Експозиција"</string>
     <string name="sharpness" msgid="6463103068318055412">"Оштрина"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Аутоматска боја"</string>
     <string name="hue" msgid="6231252147971086030">"Нијанса"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Сенке"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Истицања"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Криве"</string>
     <string name="vignette" msgid="934721068851885390">"Вињета"</string>
     <string name="redeye" msgid="4508883127049472069">"Црвене очи"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Цртање"</string>
     <string name="straighten" msgid="26025591664983528">"Исправљање"</string>
     <string name="crop" msgid="5781263790107850771">"Опсецање"</string>
     <string name="rotate" msgid="2796802553793795371">"Ротирај"</string>
     <string name="mirror" msgid="5482518108154883096">"Огледало"</string>
+    <string name="negative" msgid="6998313764388022201">"Негатив"</string>
     <string name="none" msgid="6633966646410296520">"Ниједно"</string>
+    <string name="edge" msgid="7036064886242147551">"Ивице"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Ворхол"</string>
+    <string name="downsample" msgid="3552938534146980104">"Смањи"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Црвена"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Зелена"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Плава"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Стил"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Величина"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Боја"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Линије"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Означивач"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Прскање"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Обриши"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Изабери прилагођену боју"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Избор боје"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Избор величине"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Потврди"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Оригинална"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Резултат"</string>
 </resources>
diff --git a/res/values-sr/photoeditor_strings.xml b/res/values-sr/photoeditor_strings.xml
deleted file mode 100644
index 31047c4..0000000
--- a/res/values-sr/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Фото-студио"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Није могуће учитати слику"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Није могуће сачувати измењену слику"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Измењена слика је сачувана у <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Желите да одбаците несачуване промене?"</string>
-    <string name="yes" msgid="5402582493291792293">"Да"</string>
-    <string name="save" msgid="5516670392524294967">"САЧУВАЈ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Аутоподешавање"</string>
-    <string name="crop" msgid="7598378507763334041">"Опсецање"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Унакрсна обрада"</string>
-    <string name="documentary" msgid="50396326708699797">"Документарно"</string>
-    <string name="doodle" msgid="1686409894518940990">"Дудл логотип"</string>
-    <string name="duotone" msgid="8145893940788467106">"Дуо-тон"</string>
-    <string name="facelift" msgid="6205748523156172637">"Сјај лица"</string>
-    <string name="facetan" msgid="4412831806626044111">"Преплануло лице"</string>
-    <string name="filllight" msgid="2644989991700022526">"Светлина"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Рибље око"</string>
-    <string name="flip" msgid="2357692401826287480">"Обрни"</string>
-    <string name="grain" msgid="7487585304579789098">"Зрнастост филма"</string>
-    <string name="grayscale" msgid="7641563843060945228">"црно-бело"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Црно-бело"</string>
-    <string name="highlight" msgid="3902653944386623972">"Истицање"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Ломографски"</string>
-    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
-    <string name="posterize" msgid="4139212359561383385">"Постеризуј"</string>
-    <string name="redeye" msgid="4958448806369928239">"Црвене очи"</string>
-    <string name="rotate" msgid="6607597269792373083">"Ротирај"</string>
-    <string name="saturation" msgid="8621322012271169931">"Засићење"</string>
-    <string name="sepia" msgid="7978093531824705601">"Сепија"</string>
-    <string name="shadow" msgid="8235188588101973090">"Сенке"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Изоштри"</string>
-    <string name="straighten" msgid="5217801513491493491">"Исправи"</string>
-    <string name="temperature" msgid="1607987938521534517">"Топлота"</string>
-    <string name="tint" msgid="154435943863418434">"Сенка"</string>
-    <string name="vignette" msgid="7648125924662648282">"Вињета"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Превуците означиваче да бисте опсекли фотографију"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Превуците слику на дудл логотип"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Превуците фотографију да бисте је окренули"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Додирните црвене очи да бисте их поправили"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Превуците фотографију да бисте је ротирали"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Превуците фотографију да бисте је исправили"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ефекти експозиције"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Ефекти боја"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Уметнички ефекти"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Поправи"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Опозови"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Понови"</string>
-</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index cb89f10..0f4a26f 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Ротирај удесно"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Није могуће пронаћи ставку."</string>
     <string name="edit" msgid="1502273844748580847">"Измени"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Једноставне измене"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Обрада захтева за кеширање"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кеширање..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Опсеци"</string>
     <string name="trim_action" msgid="703098114452883524">"Скрати"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Искључи звук"</string>
     <string name="set_as" msgid="3636764710790507868">"Постави као"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Звук не може да се искључи."</string>
     <string name="video_err" msgid="7003051631792271009">"Није могуће пустити видео."</string>
     <string name="group_by_location" msgid="316641628989023253">"Према локацији"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Према времену"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Аутом."</string>
     <string name="flash_on" msgid="7891556231891837284">"Блиц је актив."</string>
     <string name="flash_off" msgid="1445443413822680010">"Без блица"</string>
+    <string name="unknown" msgid="3506693015896912952">"Непознато"</string>
     <string name="ffx_original" msgid="372686331501281474">"Оригинална"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Спољна меморија није доступна"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Приказ филмске траке"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Приказ мреже"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Приказ целог екрана"</string>
     <string name="trimming" msgid="9122385768369143997">"Скраћивање"</string>
+    <string name="muting" msgid="5094925919589915324">"Искључивање звука"</string>
     <string name="please_wait" msgid="7296066089146487366">"Сачекајте"</string>
-    <string name="save_into" msgid="4960537214388766062">"Чување скраћеног видео снимка у албум:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Чување видео снимка у <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Скраћивање није могуће: циљни видео је прекратак"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Приказивање панораме"</string>
     <string name="save" msgid="613976532235060516">"Сачувај"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Скенирање садржаја..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Скенирано је %1$d ставки"</item>
+    <item quantity="one" msgid="4340019444460561648">"Скенирана је %1$d ставка"</item>
+    <item quantity="other" msgid="3138021473860555499">"Скенирано је %1$d ставки"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Сортирање..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Скенирање је завршено"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Увоз..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Нема доступног садржаја за увоз на овом уређају."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"MTP уређај није повезан"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Грешка камере"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Није могуће повезати се са камером."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Камера је онемогућена због смерница за безбедност."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Камера"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Камкордер"</string>
+    <string name="wait" msgid="8600187532323801552">"Сачекајте…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Прикључите USB меморију пре коришћења камере."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Уметните SD картицу пре коришћења камере."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Припрема USB меморије…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Припремање SD картице…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Није било могуће приступити USB меморији."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Није било могуће приступити SD картици."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ОТКАЖИ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ГОТОВО"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Снимањe у дужем интервалу"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Избор камере"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Назад"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Напред"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Чување локац."</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"ЛОКАЦИЈА"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Тајмер за одбројавање"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 секунда"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d сек"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Звучно одбројавање"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Искључено"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Укључено"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Квалитет видео снимка"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Висок"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Низак"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Снимање у дужем интервалу"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Подешавања камере"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Подешавања камкордера"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Величина слике"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 мегапискела"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 мегапиксела (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 мегапиксела"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 мегапиксел"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Режим фокуса"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Аутоматски"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Бесконачност"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Макро"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"АУТОМАТСКИ"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"БЕСКОНАЧНОСТ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"МАКРО"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Режим блица"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"РЕЖИМ БЛИЦА"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Аутоматски"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Укључено"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Искључено"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"АУТОМАТСКИ БЛИЦ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"УКЉУЧИ БЛИЦ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"ИСКЉУЧИ БЛИЦ"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Баланс беле боје"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"БАЛАНС БЕЛЕ"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Аутоматски"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Усијано"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Дневна светлост"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Флуоресцентно"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Облачно"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"АУТОМАТСКИ"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"УСИЈАНО"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ДНЕВНА СВЕТЛОСТ"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ФЛУОРЕСЦЕНТНО"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ОБЛАЧНО"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Режим сцене"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Аутоматски"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Покрет"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ноћ"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Залазак сунца"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Журка"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"НИШТА"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"АКЦИЈА"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"НОЋ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ЗАЛАЗАК СУНЦА"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ЖУРКА"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ТАЈМЕР ЗА ОДБРОЈАВАЊЕ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ТАЈМЕР ЈЕ ИСКЉУЧЕН"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 СЕКУНДА"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 СЕКУНДЕ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 СЕКУНДИ"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 СЕКУНДИ"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Ово не може да се изабере у режиму сцене."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Видљивост"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ЕКСПОЗИЦИЈА"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ПРЕДЊА КАМЕРА"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ЗАДЊА КАМЕРА"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Потврди"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"На вашој USB меморији понестаје места. Промените подешавања квалитета или избришите неке слике или друге датотеке."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"На вашој SD картици понестаје места. Промените поставке квалитета или избришите неке слике или друге датотеке."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Достигнуто је ограничење величине."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Пребрзо"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Припремање панораме"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Није могуће сачувати панораму."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Панорама"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Снимање панораме"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Чека се претходна панорама"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Чување..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Приказивање панораме"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Додирните за фокусирање."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Ефекти"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ништа"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Стисни"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Велике очи"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Велика уста"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Мала уста"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Велики нос"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Мале очи"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"У свемиру"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Залазак сунца"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Ваш видео"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Спустите уређај."\n"Изађите из кадра на тренутак."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Додирните да бисте направили фотографију током снимања."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Снимање видео садржаја је започето."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Снимање видео садржаја је заустављено."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Видео снимак је онемогућен када су специјални ефекти укључени."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Обриши ефекте"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"СМЕШНА ЛИЦА"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ПОЗАДИНА"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Дугме затварача"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Дугме менија"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Најновија фотографија"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Промена предње и задње камере"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Бирач камере, видео снимка или панораме"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Још контрола подешавања"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Затвори контроле подешавања"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Контрола зума"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Смањи %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Повећај %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Поље за потврду %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Пребаци на фотографију"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Пребаци на видео"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Пребаци на панораму"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Пребаци на нову панораму"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Откажи у режиму прегледа"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Довршено у режиму прегледа"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Поново сними за преглед"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Пусти видео"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Паузирај видео"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Поново учитај видео"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Трака за време видео плејера"</string>
+    <string name="capital_on" msgid="5491353494964003567">"УКЉУЧEНO"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ИСКЉУЧEНO"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Искључено"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 секунда"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 секунде"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 секунде"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 секунде"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 секунде"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 минут"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 минута"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 сат"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 сата"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 сата"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 сата"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 сати"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 сата"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"секунди"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"минута"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"сати"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Готово"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Подеси временски интервал"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Функција снимања у дужем интервалу је искључена. Укључите је да бисте подесили временски интервал."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Тајмер за одбројавање је искључен. Укључите га за одбројавање пре снимања слике."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Подеси трајање у секундама"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Одбројавање за снимање слике"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Желите ли да запамтите локације слика?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Додајте сликама и видео снимцима ознаке са местима где су снимљени."\n\n"Друге апликације могу да приступе овим информацијама заједно са сачуваним сликама."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Не, хвала"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Да"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Претражи"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Слике"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Албуми"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ЈОШ ОПЦИЈА"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"ПОДЕШАВАЊА"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d слика"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d слика"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sv/filtershow_strings.xml b/res/values-sv/filtershow_strings.xml
index 46964c2..3286e80 100644
--- a/res/values-sv/filtershow_strings.xml
+++ b/res/values-sv/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Det går inte att läsa in bilden."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Bakgrund anges"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Det gick inte att hämta fotot. Nätverket är inte tillgängligt."</string>
     <string name="original" msgid="3524493791230430897">"Original"</string>
     <string name="borders" msgid="2067345080568684614">"Ramar"</string>
-    <string name="done" msgid="3112344807927554662">"Klar"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Ångra"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Gör om"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Visa historik"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Dölj historik"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Visa bildläge"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Dölj bildläge"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Visa tillämpade effekter"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Dölj tillämpade effekter"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Inställningar"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Det finns ändringar i bilden som inte har sparats."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Vill du spara innan du avslutar?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Spara och avsluta"</string>
+    <string name="exit" msgid="242642957038770113">"Avsluta"</string>
     <string name="history" msgid="455767361472692409">"Historik"</string>
     <string name="reset" msgid="9013181350779592937">"Återställ"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Aktuellt bildläge"</string>
+    <string name="imageState" msgid="8632586742752891968">"Effekter som används"</string>
     <string name="compare_original" msgid="8140838959007796977">"Jämför"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Använd"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Återställ"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Ingen"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Fast"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Exponering"</string>
     <string name="sharpness" msgid="6463103068318055412">"Skärpa"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autofärg"</string>
     <string name="hue" msgid="6231252147971086030">"Nyans"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Skuggor"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Högdagrar"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Kurvor"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Röda ögon"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Rita"</string>
     <string name="straighten" msgid="26025591664983528">"Räta ut"</string>
     <string name="crop" msgid="5781263790107850771">"Beskär"</string>
     <string name="rotate" msgid="2796802553793795371">"Rotera"</string>
     <string name="mirror" msgid="5482518108154883096">"Spegel"</string>
+    <string name="negative" msgid="6998313764388022201">"Negativ"</string>
     <string name="none" msgid="6633966646410296520">"Inga"</string>
+    <string name="edge" msgid="7036064886242147551">"Kanter"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Nedsampla"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Röd"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Grön"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Blå"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Storlek"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Färg"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Linjer"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Märkpenna"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Stänk"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Rensa"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Välj anpassad färg"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Välj färg"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Välj storlek"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Original"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resultat"</string>
 </resources>
diff --git a/res/values-sv/photoeditor_strings.xml b/res/values-sv/photoeditor_strings.xml
deleted file mode 100644
index a6e5c7b..0000000
--- a/res/values-sv/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotostudio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Det gick inte att läsa in bilden"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Det gick inte att spara redigerad bild"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Den redigerade bilden sparas i <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Vill du ignorera ändringar som inte har sparats?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ja"</string>
-    <string name="save" msgid="5516670392524294967">"SPARA"</string>
-    <string name="autofix" msgid="1663414996270538748">"Åtgärda automatiskt"</string>
-    <string name="crop" msgid="7598378507763334041">"Beskär"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Cross Process"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentär"</string>
-    <string name="doodle" msgid="1686409894518940990">"Klotter"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duoton"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Fyll belysning"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Vänd"</string>
-    <string name="grain" msgid="7487585304579789098">"Filmkornighet"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Svartvitt"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Svartvitt"</string>
-    <string name="highlight" msgid="3902653944386623972">"Högdager"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativ"</string>
-    <string name="posterize" msgid="4139212359561383385">"Färgreduktion"</string>
-    <string name="redeye" msgid="4958448806369928239">"Röda ögon"</string>
-    <string name="rotate" msgid="6607597269792373083">"Rotera"</string>
-    <string name="saturation" msgid="8621322012271169931">"Mättnad"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Skuggor"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Skarpare"</string>
-    <string name="straighten" msgid="5217801513491493491">"Räta ut"</string>
-    <string name="temperature" msgid="1607987938521534517">"Värme"</string>
-    <string name="tint" msgid="154435943863418434">"Ton"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Dra markörerna om du vill beskära bilden"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Rita på bilden om du vill"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Tryck och dra bilden om du vill vända den"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Knacka lätt på röda ögon för att ta bort"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Tryck och dra bilden om du vill rotera den"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Tryck och dra bilden om du vill räta upp den"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Exponeringseffekter"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Färgeffekter"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Konstnärliga effekter"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Korrigera"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Ångra"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Gör om"</string>
-</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 171e452..5ad0485 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Rotera åt höger"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Det gick inte att hitta objektet."</string>
     <string name="edit" msgid="1502273844748580847">"Redigera"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Enkel redigering"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Begäran om cachelagring bearbetas"</string>
     <string name="caching_label" msgid="4521059045896269095">"Cachelagrar ..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Beskär"</string>
     <string name="trim_action" msgid="703098114452883524">"Beskär"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Ljud av"</string>
     <string name="set_as" msgid="3636764710790507868">"Använd som"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Ljud av misslyckades."</string>
     <string name="video_err" msgid="7003051631792271009">"Det går inte att spela upp videon."</string>
     <string name="group_by_location" msgid="316641628989023253">"Efter plats"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Efter tid"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Blixt utlöst"</string>
     <string name="flash_off" msgid="1445443413822680010">"Ingen blixt"</string>
+    <string name="unknown" msgid="3506693015896912952">"Okänt"</string>
     <string name="ffx_original" msgid="372686331501281474">"Original"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Inget externt lagringsutrymme är tillgängligt"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmremsevy"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Rutnätsvy"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Helskärmsvy"</string>
     <string name="trimming" msgid="9122385768369143997">"Beskärning"</string>
+    <string name="muting" msgid="5094925919589915324">"Stänger av ljud"</string>
     <string name="please_wait" msgid="7296066089146487366">"Vänta"</string>
-    <string name="save_into" msgid="4960537214388766062">"Spara redigera videor i albumet:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Sparar videon i <xliff:g id="ALBUM_NAME">%1$s</xliff:g> ..."</string>
     <string name="trim_too_short" msgid="751593965620665326">"Det gick inte att beskära videon. Målvideon är för kort"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panoramabild hämtas"</string>
     <string name="save" msgid="613976532235060516">"Spara"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Skannar innehåll ..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d objekt har skannats"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d objekt har skannats"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d objekt har skannats"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sorterar ..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Skannat"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Importerar ..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Det finns inget tillgängligt innehåll att importera på den här enheten."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Det finns ingen ansluten MTP-enhet"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamerafel"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Det går inte att ansluta till kameran."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kameran har inaktiverats på grund av gällande säkerhetspolicyer."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Videokamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Vänta…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Sätt i USB-lagringsenheten innan du använder kameran."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Sätt i ett SD-kort innan du använder kameran."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB-lagring förbereds…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Förbereder SD-kort…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Det gick inte att komma åt USB-enheten."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Det gick inte att öppna SD-kortet."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"AVBRYT"</string>
+    <string name="review_ok" msgid="1156261588693116433">"KLAR"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Intervallinspelning"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Välj kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Bakre"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Främre"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Spara plats"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"PLATS"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Nedräkningstimer"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 sekund"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d sekunder"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Ljudsignal"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Inaktiverad"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Aktiverad"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Videokvalitet"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Hög"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Låg"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Intervallinspelning"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kamerainställningar"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Videokamerainställningar"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Bildstorlek"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 mp (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 megapixlar"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 megapixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Fokusläge"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Automatiskt"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Oändligt"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"OÄNDLIGT"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Blixtläge"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"BLIXTLÄGE"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Automatiskt"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"På"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Av"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"AUTOMATISK BLIXT"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BLIXT PÅ"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"AVSTÄNGD BLIXT"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Vitbalans"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"VITBALANS"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Automatiskt"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Självlysande"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Dagsljus"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescerande"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Molnigt"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"SJÄLVLYSANDE"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAGSLJUS"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCERANDE"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"MOLNIGT"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scenläge"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Automatiskt"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Action"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Natt"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Solnedgång"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Fest"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"INGA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACTION"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NATT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SOLNEDGÅNG"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"FEST"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"NEDRÄKNINGSTIMER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TIMER AV"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEKUND"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEKUNDER"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEKUNDER"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Inte valbart i scenläget."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Exponering"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPONERING"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"FRÄMRE KAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"BAKRE KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Din USB-lagringsenhet börjar bli full. Ändra kvalitetsinställningen eller ta bort några bilder eller andra filer."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Den delade lagringsenheten börjar bli full. Ändra inställningen för kvalitet eller ta bort några bilder eller andra filer."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Storleksgränsen nådd."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"För snabbt"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Förbereder panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Det gick inte att spara panoramabild."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Ta panoramafoto"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Väntar på föregående panoramabild"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Sparar ..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panoramabild hämtas"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Fokusera genom att trycka."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Effekter"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Ingen"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Hopklämning"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Stora ögon"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Stor mun"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Liten mun"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Stor näsa"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Små ögon"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"I rymden"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Solnedgång"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Din video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Lägg ned enheten"\n"Ställ dig utom synhåll för ett ögonblick."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Tryck om du vill ta ett foto medan du spelar in."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Videoinspelningen har börjat."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Videoinspelningen har stoppats."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Ögonblicksbilden av en video inaktiveras när specialeffekter är aktiverade."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Ta bort effekter"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"ROLIGA GRIMASER"</string>
+    <string name="effect_background" msgid="6579360207378171022">"BAKGRUND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Slutarknappen"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menyknapp"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Senaste fotot"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Kameraläge framåt/bakåt"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Väljare för kamera, video och panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Fler inställningskontroller"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Stäng inställningskontrollerna"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zoomkontroll"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Minska %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Öka %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Markeringsrutan %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Byt till kameraläge"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Byt till video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Byt till panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Byt till det nya panoramaläget"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Avbryt"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Klar"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Omtagning"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Spela upp videoklipp"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Pausa videoklippet"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Läs in video igen"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Videospelarens tidsindikator"</string>
+    <string name="capital_on" msgid="5491353494964003567">"PÅ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"AV"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Av"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 sekund"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 sekunder"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minut"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 minuter"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 timme"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 timmar"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 timmar"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunder"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"minuter"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"timmar"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Klar"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Ange tidssintervall"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Intervallinspelningsfunktionen är avstängd. Aktivera den om du vill ange tidsintervall."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Nedräkningstimern är inaktiverad. Aktivera den om du vill att kameran ska räkna ned innan en bild tas."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Ange längden i sekunder"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Nedräkning innan fotot tas"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Vill du spara platser för foton?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Tagga dina foton och videor med de platser där de tas eller spelas in."\n\n"Andra appar kan få åtkomst till den här informationen tillsammans med dina sparade bilder."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Nej tack"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ja"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Sök"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Foton"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"FLER ALTERNATIV"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"INSTÄLLNINGAR"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d foto"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d foton"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sw/filtershow_strings.xml b/res/values-sw/filtershow_strings.xml
index 30aab76..6909b26 100644
--- a/res/values-sw/filtershow_strings.xml
+++ b/res/values-sw/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Haiwezi kupakia picha!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Inaweka mandhari"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Haikuweza kupakua picha. Mtandao haupatikani."</string>
     <string name="original" msgid="3524493791230430897">"Asili"</string>
     <string name="borders" msgid="2067345080568684614">"Kingo"</string>
-    <string name="done" msgid="3112344807927554662">"Umekamilisha"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Tendua"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Rudia"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Onyesha Historia"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ficha Historia"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Onyesha Hali ya Picha"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ficha Hali ya Picha"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Onyesha Athari ambazo Zimetekelezwa"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ficha Athari ambazo Zimetekelezwa"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Mipangilio"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Kuna mabadiliko ambayo hayajahifadhiwa ya picha hii."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Je, unataka kuhifadhi kabla hujaondoka?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Hifadhi na Uondoke"</string>
+    <string name="exit" msgid="242642957038770113">"Ondoka"</string>
     <string name="history" msgid="455767361472692409">"Historia"</string>
     <string name="reset" msgid="9013181350779592937">"Weka upya"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Hali ya Sasa ya Picha"</string>
+    <string name="imageState" msgid="8632586742752891968">"Madoido Yanayotumiwa"</string>
     <string name="compare_original" msgid="8140838959007796977">"Linganisha"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Tekeleza"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Weka upya"</string>
@@ -49,8 +52,9 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Bila"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Imerekebishwa"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Sayari Ndogo zaidi"</string>
-    <string name="exposure" msgid="6526397045949374905">"Uwekaji wazi"</string>
+    <string name="exposure" msgid="6526397045949374905">"Kiasi cha mwangaza"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ung\'aavu"</string>
     <string name="contrast" msgid="2310908487756769019">"Ulinganuzi"</string>
     <string name="vibrance" msgid="3326744578577835915">"Ya kusisimua"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Rangi otomatiki"</string>
     <string name="hue" msgid="6231252147971086030">"Rangi"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Vivuli"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Sehemu zenye ung\'avu zaidi"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Pindo"</string>
     <string name="vignette" msgid="934721068851885390">"Vignete"</string>
     <string name="redeye" msgid="4508883127049472069">"Jicho Jekundu"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Chora"</string>
     <string name="straighten" msgid="26025591664983528">"Nyoosha"</string>
     <string name="crop" msgid="5781263790107850771">"Puna"</string>
     <string name="rotate" msgid="2796802553793795371">"Zungusha"</string>
     <string name="mirror" msgid="5482518108154883096">"Kioo"</string>
+    <string name="negative" msgid="6998313764388022201">"Hasi"</string>
     <string name="none" msgid="6633966646410296520">"Umekamilisha"</string>
+    <string name="edge" msgid="7036064886242147551">"Pambizoni"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Sampuli ndogo"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Nyekundu"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Kijani"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Samawati"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Mtindo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Ukubwa"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Rangi"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Mistari"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Kialamisho"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Tapanya"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Futa"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Chagua rangi maalum"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Chagua Rangi"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Chagua Ukubwa"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"SAWA"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Asili"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Matokeo"</string>
 </resources>
diff --git a/res/values-sw/photoeditor_strings.xml b/res/values-sw/photoeditor_strings.xml
deleted file mode 100644
index 5b98395..0000000
--- a/res/values-sw/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Studio ya Picha"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Haikuweza kupakia picha"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Haikuweza kuhifadhi picha iliyohaririwa"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Picha iliyohaririwa imehifadhiwa kwa <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Tupa mabadiliko yasiyohifadhiwa?"</string>
-    <string name="yes" msgid="5402582493291792293">"Ndio"</string>
-    <string name="save" msgid="5516670392524294967">"HIFADHI"</string>
-    <string name="autofix" msgid="1663414996270538748">"Weka kiotomatiki"</string>
-    <string name="crop" msgid="7598378507763334041">"Kata"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Mchakato wa kati"</string>
-    <string name="documentary" msgid="50396326708699797">"Ya hali halisi"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duo-toni"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Uso Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Jaza Mwangaza"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Flipu"</string>
-    <string name="grain" msgid="7487585304579789098">"Nafaka ya Filamu"</string>
-    <string name="grayscale" msgid="7641563843060945228">"B &amp; W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Nyeusi na nyeupe"</string>
-    <string name="highlight" msgid="3902653944386623972">"Angazisho"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negativu"</string>
-    <string name="posterize" msgid="4139212359561383385">"Weka kwenye bango"</string>
-    <string name="redeye" msgid="4958448806369928239">"Jicho Jekundu"</string>
-    <string name="rotate" msgid="6607597269792373083">"Zungusha"</string>
-    <string name="saturation" msgid="8621322012271169931">"Yenye maji"</string>
-    <string name="sepia" msgid="7978093531824705601">"Rangi ya Ngisi"</string>
-    <string name="shadow" msgid="8235188588101973090">"Vivuli"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Punguza"</string>
-    <string name="straighten" msgid="5217801513491493491">"Tiririsha"</string>
-    <string name="temperature" msgid="1607987938521534517">"Joto"</string>
-    <string name="tint" msgid="154435943863418434">"Tinti"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Kokota viweka alama ili kupuna picha"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Chora kwenye picha ku doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Kokota picha ili kuiflipu"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Gonga kwenye macho mekundu ili kuyaondoa"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"kokota picha ili kuizungusha"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Kokota picha ili kuizungusha"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Athari za kiwango cha mwangaza"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Athari za rangi"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Athari za kisanii"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Rekebisha"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Tendua"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Rudia"</string>
-</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 72a0d99..6025bf8 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -91,11 +91,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Zungusha kulia"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Hakuweza kupata kipengee."</string>
     <string name="edit" msgid="1502273844748580847">"Hariri"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Kuhariri Rahisi"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Maombi ya kuakibisha michakato"</string>
     <string name="caching_label" msgid="4521059045896269095">"Inaakibisha..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Punguza"</string>
     <string name="trim_action" msgid="703098114452883524">"Punguza"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Zima sauti"</string>
     <string name="set_as" msgid="3636764710790507868">"Weka kama"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Imeshindwa kuzima sauti ya video."</string>
     <string name="video_err" msgid="7003051631792271009">"Video haiwezi kuchezwa."</string>
     <string name="group_by_location" msgid="316641628989023253">"Kwa mahali"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Kwa saa"</string>
@@ -116,7 +119,7 @@
     <string name="no_albums_alert" msgid="4111744447491690896">"Hakuna albamu zinazopatikana."</string>
     <string name="empty_album" msgid="4542880442593595494">"picha / video zilizopo 0."</string>
     <string name="picasa_posts" msgid="1497721615718760613">"Zilizowekwa"</string>
-    <string name="make_available_offline" msgid="5157950985488297112">"Fanya ipatikane nje ya mkondo"</string>
+    <string name="make_available_offline" msgid="5157950985488297112">"Fanya ipatikane nje ya mtandao"</string>
     <string name="sync_picasa_albums" msgid="8522572542111169872">"Onyesha upya"</string>
     <string name="done" msgid="217672440064436595">"Kwisha"</string>
     <string name="sequence_in_set" msgid="7235465319919457488">"Vipengee %1$d kati ya %2$d:"</string>
@@ -134,16 +137,17 @@
     <string name="maker" msgid="7921835498034236197">"Mtengenezaji"</string>
     <string name="model" msgid="8240207064064337366">"Mtindo"</string>
     <string name="flash" msgid="2816779031261147723">"Mmweko"</string>
-    <string name="aperture" msgid="5920657630303915195">"Kilango"</string>
+    <string name="aperture" msgid="5920657630303915195">"Kitundu cha kamera"</string>
     <string name="focal_length" msgid="1291383769749877010">"Urefu wa Lengo"</string>
     <string name="white_balance" msgid="1582509289994216078">"Usawazishaji wa weupe"</string>
-    <string name="exposure_time" msgid="3990163680281058826">"Muda wa Mfichuo"</string>
+    <string name="exposure_time" msgid="3990163680281058826">"Muda ambao kilango cha kamera kinakaa wazi"</string>
     <string name="iso" msgid="5028296664327335940">"ISO"</string>
     <string name="unit_mm" msgid="1125768433254329136">"mm"</string>
     <string name="manual" msgid="6608905477477607865">"Mwongozo"</string>
     <string name="auto" msgid="4296941368722892821">"Kiotomatiki"</string>
     <string name="flash_on" msgid="7891556231891837284">"Mmweko umeanzishwa"</string>
     <string name="flash_off" msgid="1445443413822680010">"Hakuna flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Usiojulikana"</string>
     <string name="ffx_original" msgid="372686331501281474">"Asili"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Ukale"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Papo hapo"</string>
@@ -167,7 +171,7 @@
     <string name="size_below" msgid="2074956730721942260">"<xliff:g id="SIZE">%1$s</xliff:g> au chini"</string>
     <string name="size_above" msgid="5324398253474104087">"<xliff:g id="SIZE">%1$s</xliff:g> au juu"</string>
     <string name="size_between" msgid="8779660840898917208">"<xliff:g id="MIN_SIZE">%1$s</xliff:g> kwa <xliff:g id="MAX_SIZE">%2$s</xliff:g>"</string>
-    <string name="Import" msgid="3985447518557474672">"Leta"</string>
+    <string name="Import" msgid="3985447518557474672">"Ingiza"</string>
     <string name="import_complete" msgid="3875040287486199999">"Kuingiza Kumekamilika"</string>
     <string name="import_fail" msgid="8497942380703298808">"Haijafaulu kuleta"</string>
     <string name="camera_connected" msgid="916021826223448591">"Kamera imeunganishwa."</string>
@@ -179,7 +183,7 @@
     <string name="widget_type" msgid="1364653978966343448">"Chagua picha"</string>
     <string name="slideshow_dream_name" msgid="6915963319933437083">"Onyesho la slaidi"</string>
     <string name="albums" msgid="7320787705180057947">"Albamu"</string>
-    <string name="times" msgid="2023033894889499219">"Nyakati"</string>
+    <string name="times" msgid="2023033894889499219">"Wakati"</string>
     <string name="locations" msgid="6649297994083130305">"Mahali"</string>
     <string name="people" msgid="4114003823747292747">"Watu"</string>
     <string name="tags" msgid="5539648765482935955">"Lebo"</string>
@@ -196,10 +200,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Hakuna hifadhi ya nje inayopatikana"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Mwonekano wa utepe wa filamu"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Mwonekano wa gridi"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Mwonekano wa skrini nzima"</string>
     <string name="trimming" msgid="9122385768369143997">"Inapunguza"</string>
+    <string name="muting" msgid="5094925919589915324">"Inanyamazisha"</string>
     <string name="please_wait" msgid="7296066089146487366">"Tafadhali subiri"</string>
-    <string name="save_into" msgid="4960537214388766062">"Inahifadhi video zilizopunguzwa kwenye albamu:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Inahifadhi video kwenye <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Haiwezi kupunguza : video iliyolengwa ni fupi sana"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Inaonyesha panorama"</string>
     <string name="save" msgid="613976532235060516">"Hifadhi"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Maudhui yanachanganuliwa..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d vipengee vimechanganuliwa"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d kipengee kimechanganuliwa"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d vipengee vimechanganuliwa"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Inapanga..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Uchanganuzi umefanyika"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Inaingiza..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Hakuna maudhui yanayopatikana kuingiza kwenye kifaa hiki."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Hakuna kifaa cha MTP kilichounganishwa"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Hitilafu ya kamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Haiwezi kuungana na kamera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera imelemazwa kwa sababu ya sera za usalama."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Kamkoda"</string>
+    <string name="wait" msgid="8600187532323801552">"Tafadhali subiri…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Pachika hifadhi ya USB kabla ya kutumia kamera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Chomeka kadi ya SD kabla ya kutumia kamera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Inaandaa hifadhi ya USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Inatayarisha kadi ya SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Haikuweza kufikia hifadhi ya USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Haikuweza kufikia kadi ya SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"GHAIRI"</string>
+    <string name="review_ok" msgid="1156261588693116433">"IMEFANYWA"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Rekodi ya kupita kwa muda"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Chagua kamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Nyuma"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Mbele"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Rekodi mahali"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"MAHALI"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Kipima muda"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"Sekunde 1"</item>
+    <item quantity="other" msgid="6455381617076792481">"Sekunde %d"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Toa mlio inapohesabu"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Imezimwa"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Imewashwa"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Ubora wa video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Juu"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Chini"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Kupita kwa muda"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Mipangilio ya kamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Mipangilio ya kamkoda"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Ukubwa wa picha"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"Pikseli 13M"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"Pikseli 8M"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"Pikseli 5M"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"Pikseli za 4M"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"Pikseli 3M"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"Pikseli 2M"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"Pikseli za 2M  (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"Pikseli 1.3M"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"Pikseli 1M"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Hali ya kulenga"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Otomatiki"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Pasipo mwisho"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"OTOMATIKI"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"PASIPO MWISHO"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Hali ya mweka"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"HALI YA MWEKO"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Otomatiki"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Washa"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Zima"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"MWEKA OTOMATIKI"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"MWAKO UMEWASHWA"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"MWAKO UMEZIMWA"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Usawazishaji wa weupe"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"USAWAZISHAJI WA WEUPE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Kioto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"King\'arishaji"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Mwangaza wa mchana"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Kiakisi mwanga"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Mawingu"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"OTOMATIKI"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"KING\'ARISHAJI"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"MCHANA"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"KIAKISI MWANGA"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"MAWINGU"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Hali ya mandhari"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Otomatiki"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Kitendo"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Usiku"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Machweo"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Karamu"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"HAKUNA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"KITENDO"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"USIKU"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"MACHWEO"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"SHEREHE"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"KIPIMA MUDA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"KIPIMA WAKATI KIMEZIMWA"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"SEKUNDE 1"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"SEKUNDE 3"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"SEKUNDE 10"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"SEKUNDE 15"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Haiwezi kuchaguliwa ikiwa katika hali ya mandhari."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Kiasi cha mwangaza"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"MFICHUO"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"KAMERA YA MBELE"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"KAMERA YA NYUMA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"SAWA"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Hifadhi yako ya USB inaishiwa na nafasi. Badilisha mipangilio ya ubora au futa baadhi ya picha au faili."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Nafasi ya kadi yako ya SD inaelekea kuisha. Badlisha mpangilio wa ubora au futa baadhi ya picha au faili zingine."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Upeo wa ukubwa umefikiwa."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Haraka mno"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panorama inaandaliwa"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Imeshindwa kuhifadhi picha ya panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Inapiga picha ya panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Inasubiri picha ya awali ya panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Inahifadhi…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Inaonyesha panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Gusa ili kulenga."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Athari"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Hamna"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Finya"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Macho makubwa"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Mdomo mkubwa"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Mdomo mdogo"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Pua kubwa"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Macho Madogo"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Kwenya nafasi"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Jua kutua"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Video yako"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Weka kifaa chako chini."\n"Toka nje ya muonekano kwa muda."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Gusa ili upige picha wakati unarekodi."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Kurekodi kwa video kumeanza."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Kurekodi kwa video kumekomeshwa."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Picha ya video imelemazwa wakati athari maalum imewashwa."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Athari Wazi"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"SURA PUMBAVU"</string>
+    <string name="effect_background" msgid="6579360207378171022">"USULI"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Kitufe cha kilango cha kamera"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Kitufe cha menyu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Picha ya hivi karibuni"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Swichi ya mbele na nyuma ya kamera"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kichagua kamera, video au Picha ya mandhari"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Vidhibiti zaidi vya mpangilio"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Funga vidhibiti mipangilio"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Dhibiti kukuza"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Punguza %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Ongeza %1$s"</string>
+    <!-- String.format failed for translation -->
+    <!-- no translation found for accessibility_check_box (7317447218256584181) -->
+    <skip />
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Badilisha hadi kwa picha"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Badilisha hadi kwa video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Badilisha hadi panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Badili hadi kwenye panorama mpya"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Ukaguzi umeghairiwa"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Ukaguzi umekamilika"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Kagua ukaguzi"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Cheza video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Sitisha video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Pakia video upya"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Ubao wa muda wa kicheza video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"IMEWASHWA"</string>
+    <string name="capital_off" msgid="7231052688467970897">"IMEZIMWA"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Imezimwa"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"Sekunde 0.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"Sekunde 1"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"Sekunde 1.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"Sekunde 2"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"Sekunde 2.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"Sekunde 3"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"Sekunde 4"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"Sekunde 5"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"Sekunde 6"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"Sekunde 10"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"Sekunde 12"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"Sekunde 15"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"Sekunde 24"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"Dakika 0.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"Dakika 1"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"Dakika 1.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"Dakika 2"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"Dakika 2.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"Dakika 3"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"Dakika 4"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"Dakika 5"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"Dakika 6"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"Dakika 1"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"Dakika 12"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"Dakika 15"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"Dakika 24"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"Saa 0.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"Saa 1"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"Saa 1.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"Saa 2"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"Saa 2.5"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"Saa 3"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"Saa 4"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"Saa 5"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"Saa 6"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"Saa 10"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"Saa 12"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"Saa 15"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"Saa 24"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"sekunde"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"dakika"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"saa"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Imekamilika"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Weka Nafasi ya Muda"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Kipengele cha muda kupita kimezimika. Kiwashe ili kuweka nafasi ya muda."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Kipima muda kimezimwa. Kiwashe ili kihesabu kabla ya kupiga picha."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Weka muda katika sekunde"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Inahesabu ili kupiga picha"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Kumbuka maeneo ya picha?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Tambulisha picha na video zako kwa maeneo ambapo zinachukuliwa."\n\n"Programu nyingine zinaweza kufikia maelezo haya kando na picha zako zilizohifadhiwa."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"La, asante"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Ndiyo"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Tafuta"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Picha"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albamu"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"CHAGUO ZAIDI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"MIPANGILIO"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"Picha %1$d"</item>
+    <item quantity="other" msgid="3813306834113858135">"Picha %1$d"</item>
+  </plurals>
 </resources>
diff --git a/res/values-sw360dp-land/dimensions.xml b/res/values-sw360dp-land/dimensions.xml
new file mode 100644
index 0000000..fe3e6f5
--- /dev/null
+++ b/res/values-sw360dp-land/dimensions.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <dimen name="capture_margin_top">8dip</dimen>
+</resources>
diff --git a/res/values-sw360dp/dimensions.xml b/res/values-sw360dp/dimensions.xml
new file mode 100644
index 0000000..b18b934
--- /dev/null
+++ b/res/values-sw360dp/dimensions.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <dimen name="capture_margin_top">16dip</dimen>
+</resources>
diff --git a/res/values-sw600dp-hdpi/drawable.xml b/res/values-sw600dp-hdpi/drawable.xml
new file mode 100644
index 0000000..b810347
--- /dev/null
+++ b/res/values-sw600dp-hdpi/drawable.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <item name="btn_video_shutter_recording_holo" type="drawable">@drawable/btn_video_shutter_recording_holo_large</item>
+    <item name="btn_video_shutter_recording_pressed_holo" type="drawable">@drawable/btn_video_shutter_recording_pressed_holo_large</item>
+    <item name="ic_effects_holo_light" type="drawable">@drawable/ic_effects_holo_light_large</item>
+    <item name="ic_pan_border_fast" type="drawable">@drawable/ic_pan_border_fast_large</item>
+    <item name="ic_pan_left_indicator_fast" type="drawable">@drawable/ic_pan_left_indicator_fast_large</item>
+    <item name="ic_pan_left_indicator" type="drawable">@drawable/ic_pan_left_indicator_large</item>
+    <item name="ic_pan_progression" type="drawable">@drawable/ic_pan_progression_large</item>
+    <item name="ic_pan_right_indicator_fast" type="drawable">@drawable/ic_pan_right_indicator_fast_large</item>
+    <item name="ic_pan_right_indicator" type="drawable">@drawable/ic_pan_right_indicator_large</item>
+    <item name="ic_scn_holo_light" type="drawable">@drawable/ic_scn_holo_light_large</item>
+    <item name="ic_snapshot_border" type="drawable">@drawable/ic_snapshot_border_large</item>
+    <item name="ic_switch_photo_facing_holo_light" type="drawable">@drawable/ic_switch_photo_facing_holo_light_large</item>
+    <item name="ic_switch_video_facing_holo_light" type="drawable">@drawable/ic_switch_video_facing_holo_light_large</item>
+    <item name="ic_timelapse_none" type="drawable">@drawable/ic_timelapse_none_large</item>
+    <item name="list_divider" type="drawable">@drawable/list_divider_large</item>
+</resources>
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..5ec2f19
--- /dev/null
+++ b/res/values-sw600dp/dimens.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <dimen name="setting_popup_right_margin">@dimen/setting_popup_right_margin_large</dimen>
+    <dimen name="setting_row_height">@dimen/setting_row_height_large</dimen>
+    <dimen name="setting_popup_window_width">@dimen/setting_popup_window_width_large</dimen>
+    <dimen name="setting_item_icon_width">@dimen/setting_item_icon_width_large</dimen>
+    <dimen name="onscreen_indicators_height">@dimen/onscreen_indicators_height_large</dimen>
+    <dimen name="shutter_offset">-33dp</dimen>
+    <dimen name="capture_size">80dip</dimen>
+    <dimen name="capture_margin_top">16dip</dimen>
+    <dimen name="camera_controls_size">520dip</dimen>
+</resources>
diff --git a/res/values-sw640dp/dimens.xml b/res/values-sw640dp/dimens.xml
new file mode 100644
index 0000000..51b3dad
--- /dev/null
+++ b/res/values-sw640dp/dimens.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <dimen name="pano_mosaic_surface_height">@dimen/pano_mosaic_surface_height_xlarge</dimen>
+    <dimen name="pano_review_button_width">@dimen/pano_review_button_width_xlarge</dimen>
+    <dimen name="pano_review_button_height">@dimen/pano_review_button_height_xlarge</dimen>
+    <dimen name="setting_row_height">@dimen/setting_row_height_xlarge</dimen>
+    <dimen name="setting_item_text_size">@dimen/setting_item_text_size_xlarge</dimen>
+    <dimen name="setting_knob_width">@dimen/setting_knob_width_xlarge</dimen>
+    <dimen name="setting_item_text_width">@dimen/setting_item_text_width_xlarge</dimen>
+    <dimen name="setting_popup_window_width">@dimen/setting_popup_window_width_xlarge</dimen>
+    <dimen name="setting_item_list_margin">@dimen/setting_item_list_margin_xlarge</dimen>
+    <dimen name="indicator_bar_width">@dimen/indicator_bar_width_xlarge</dimen>
+    <dimen name="popup_title_text_size">@dimen/popup_title_text_size_xlarge</dimen>
+    <dimen name="popup_title_frame_min_height">@dimen/popup_title_frame_min_height_xlarge</dimen>
+    <dimen name="big_setting_popup_window_width">@dimen/big_setting_popup_window_width_xlarge</dimen>
+    <dimen name="setting_item_icon_width">@dimen/setting_item_icon_width_xlarge</dimen>
+    <dimen name="effect_setting_item_icon_width">@dimen/effect_setting_item_icon_width_xlarge</dimen>
+    <dimen name="effect_setting_item_text_size">@dimen/effect_setting_item_text_size_xlarge</dimen>
+    <dimen name="effect_setting_type_text_size">@dimen/effect_setting_type_text_size_xlarge</dimen>
+    <dimen name="effect_setting_type_text_min_height">@dimen/effect_setting_type_text_min_height_xlarge</dimen>
+    <dimen name="effect_setting_clear_text_size">@dimen/effect_setting_clear_text_size_xlarge</dimen>
+    <dimen name="effect_setting_clear_text_min_height">@dimen/effect_setting_clear_text_min_height_xlarge</dimen>
+    <dimen name="effect_setting_type_text_left_padding">@dimen/effect_setting_type_text_left_padding_xlarge</dimen>
+    <dimen name="onscreen_indicators_height">@dimen/onscreen_indicators_height_xlarge</dimen>
+    <dimen name="onscreen_exposure_indicator_text_size">@dimen/onscreen_exposure_indicator_text_size_xlarge</dimen>
+</resources>
+
diff --git a/res/values-sw640dp/drawable.xml b/res/values-sw640dp/drawable.xml
new file mode 100644
index 0000000..6a6e711
--- /dev/null
+++ b/res/values-sw640dp/drawable.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <item name="btn_video_shutter_recording_holo" type="drawable">@drawable/btn_video_shutter_recording_holo_xlarge</item>
+    <item name="btn_video_shutter_recording_pressed_holo" type="drawable">@drawable/btn_video_shutter_recording_pressed_holo_xlarge</item>
+    <item name="ic_effects_holo_light" type="drawable">@drawable/ic_effects_holo_light_xlarge</item>
+    <item name="ic_pan_border_fast" type="drawable">@drawable/ic_pan_border_fast_xlarge</item>
+    <item name="ic_pan_left_indicator_fast" type="drawable">@drawable/ic_pan_left_indicator_fast_xlarge</item>
+    <item name="ic_pan_left_indicator" type="drawable">@drawable/ic_pan_left_indicator_xlarge</item>
+    <item name="ic_pan_progression" type="drawable">@drawable/ic_pan_progression_xlarge</item>
+    <item name="ic_pan_right_indicator_fast" type="drawable">@drawable/ic_pan_right_indicator_fast_xlarge</item>
+    <item name="ic_pan_right_indicator" type="drawable">@drawable/ic_pan_right_indicator_xlarge</item>
+    <item name="ic_scn_holo_light" type="drawable">@drawable/ic_scn_holo_light_xlarge</item>
+    <item name="ic_snapshot_border" type="drawable">@drawable/ic_snapshot_border_xlarge</item>
+    <item name="ic_switch_photo_facing_holo_light" type="drawable">@drawable/ic_switch_photo_facing_holo_light_xlarge</item>
+    <item name="ic_switch_video_facing_holo_light" type="drawable">@drawable/ic_switch_video_facing_holo_light_xlarge</item>
+    <item name="ic_timelapse_none" type="drawable">@drawable/ic_timelapse_none_xlarge</item>
+</resources>
diff --git a/res/values-sw640dp/styles.xml b/res/values-sw640dp/styles.xml
new file mode 100644
index 0000000..6ab7063
--- /dev/null
+++ b/res/values-sw640dp/styles.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<resources>
+    <style name="ReviewControlText" parent="@style/ReviewControlText_xlarge" />
+    <style name="PopupTitleText" parent="@style/PopupTitleText_xlarge" />
+    <style name="PanoCustomDialogText" parent="@style/PanoCustomDialogText_xlarge" />
+    <style name="ViewfinderLabelLayout" parent="@style/ViewfinderLabelLayout_xlarge" />
+    <style name="SettingPopupWindow" parent="@style/SettingPopupWindow_xlarge" />
+</resources>
diff --git a/res/values-sw800dp/dimens.xml b/res/values-sw800dp/dimens.xml
new file mode 100644
index 0000000..d1b8c6d
--- /dev/null
+++ b/res/values-sw800dp/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2011, 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.
+-->
+<resources>
+    <dimen name="setting_row_height">54dp</dimen>
+    <dimen name="setting_item_text_size">21dp</dimen>
+    <dimen name="setting_knob_width">72dp</dimen>
+    <dimen name="setting_item_text_width">130dp</dimen>
+    <dimen name="setting_popup_window_width">410dp</dimen>
+    <dimen name="setting_item_list_margin">24dp</dimen>
+    <dimen name="popup_title_text_size">22dp</dimen>
+    <dimen name="popup_title_frame_min_height">64dp</dimen>
+    <dimen name="big_setting_popup_window_width">590dp</dimen>
+    <dimen name="setting_item_icon_width">35dp</dimen>
+</resources>
diff --git a/res/values-th/filtershow_strings.xml b/res/values-th/filtershow_strings.xml
index 6fe2ff4..311e89e 100644
--- a/res/values-th/filtershow_strings.xml
+++ b/res/values-th/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"ไม่สามารถโหลดภาพ!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"กำลังตั้งค่าวอลเปเปอร์"</string>
+    <string name="download_failure" msgid="5923323939788582895">"ไม่สามารถดาวน์โหลดภาพ เครือข่ายไม่พร้อมใช้งาน"</string>
     <string name="original" msgid="3524493791230430897">"ต้นฉบับ"</string>
     <string name="borders" msgid="2067345080568684614">"ขอบ"</string>
-    <string name="done" msgid="3112344807927554662">"เสร็จสิ้น"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"เลิกทำ"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"ทำซ้ำ"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"แสดงประวัติ"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"ซ่อนประวัติ"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"สถานะแสดงภาพ"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"สถานะซ่อนภาพ"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"แสดงเอฟเฟ็กต์ที่ใช้"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"ซ่อนเอฟเฟ็กต์ที่ใช้"</string>
     <string name="menu_settings" msgid="6428291655769260831">"การตั้งค่า"</string>
+    <string name="unsaved" msgid="8704442449002374375">"มีการเปลี่ยนแปลงที่ไม่ได้บันทึกไปยังภาพนี้"</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"คุณต้องการบันทึกก่อนที่จะออกหรือไม่"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"บันทึกและออก"</string>
+    <string name="exit" msgid="242642957038770113">"ออก"</string>
     <string name="history" msgid="455767361472692409">"ประวัติ"</string>
     <string name="reset" msgid="9013181350779592937">"รีเซ็ต"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"สถานะภาพปัจจุบัน"</string>
+    <string name="imageState" msgid="8632586742752891968">"เอฟเฟ็กต์ที่ใช้"</string>
     <string name="compare_original" msgid="8140838959007796977">"เปรียบเทียบ"</string>
     <string name="apply_effect" msgid="1218288221200568947">"ใช้"</string>
     <string name="reset_effect" msgid="7712605581024929564">"รีเซ็ต"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"ไม่มี"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"คงที่"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"โลกใบเล็ก"</string>
     <string name="exposure" msgid="6526397045949374905">"การรับแสง"</string>
     <string name="sharpness" msgid="6463103068318055412">"ความคมชัด"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"ให้สีอัตโนมัติ"</string>
     <string name="hue" msgid="6231252147971086030">"สี"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"เงา"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"ไฮไลต์"</string>
     <string name="curvesRGB" msgid="915010781090477550">"เส้นโค้ง"</string>
     <string name="vignette" msgid="934721068851885390">"วิกเน็ตต์"</string>
     <string name="redeye" msgid="4508883127049472069">"ตาแดง"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"วาดภาพ"</string>
     <string name="straighten" msgid="26025591664983528">"ทำให้ตรง"</string>
     <string name="crop" msgid="5781263790107850771">"ครอบตัด"</string>
     <string name="rotate" msgid="2796802553793795371">"หมุน"</string>
     <string name="mirror" msgid="5482518108154883096">"มิเรอร์"</string>
+    <string name="negative" msgid="6998313764388022201">"เนกาทีฟ"</string>
     <string name="none" msgid="6633966646410296520">"ไม่มี"</string>
+    <string name="edge" msgid="7036064886242147551">"ขอบ"</string>
+    <string name="kmeans" msgid="1630263230946107457">"วอร์ฮอล"</string>
+    <string name="downsample" msgid="3552938534146980104">"ลดลง"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"สีแดง"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"สีเขียว"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"สีน้ำเงิน"</string>
+    <string name="draw_style" msgid="2036125061987325389">"รูปแบบ"</string>
+    <string name="draw_size" msgid="4360005386104151209">"ขนาด"</string>
+    <string name="draw_color" msgid="2119030386987211193">"สี"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"เส้น"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"สีเมจิก"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"สาดสี"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"ล้าง"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"เลือกสีที่กำหนดเอง"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"เลือกสี"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"เลือกขนาด"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"ตกลง"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"ต้นฉบับ"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"ผลลัพธ์"</string>
 </resources>
diff --git a/res/values-th/photoeditor_strings.xml b/res/values-th/photoeditor_strings.xml
deleted file mode 100644
index 0550450..0000000
--- a/res/values-th/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"สตูดิโอถ่ายภาพ"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"ไม่สามารถโหลดรูปภาพ"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"ไม่สามารถบันทึกรูปภาพที่แก้ไข"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"บันทึกภาพที่แก้ไขไว้ที่ <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"ยกเลิกการเปลี่ยนแปลงที่ยังไม่ได้บันทึก"</string>
-    <string name="yes" msgid="5402582493291792293">"ใช่"</string>
-    <string name="save" msgid="5516670392524294967">"บันทึก"</string>
-    <string name="autofix" msgid="1663414996270538748">"แก้ไขอัตโนมัติ"</string>
-    <string name="crop" msgid="7598378507763334041">"ตัด"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"ครอสโพรเซส"</string>
-    <string name="documentary" msgid="50396326708699797">"สารคดี"</string>
-    <string name="doodle" msgid="1686409894518940990">"doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"ดูโอโทน"</string>
-    <string name="facelift" msgid="6205748523156172637">"ใบหน้าใส"</string>
-    <string name="facetan" msgid="4412831806626044111">"ใบหน้าสีแทน"</string>
-    <string name="filllight" msgid="2644989991700022526">"เติมแสง"</string>
-    <string name="fisheye" msgid="6037488646928998921">"ฟิชอาย"</string>
-    <string name="flip" msgid="2357692401826287480">"ฟลิป"</string>
-    <string name="grain" msgid="7487585304579789098">"เนื้อฟิล์ม"</string>
-    <string name="grayscale" msgid="7641563843060945228">"ขาวดำ"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"ภาพขาวดำ"</string>
-    <string name="highlight" msgid="3902653944386623972">"ไฮไลต์"</string>
-    <string name="lomoish" msgid="1270032154357186736">"โลโม"</string>
-    <string name="negative" msgid="1985508917342811252">"สีตรงข้าม"</string>
-    <string name="posterize" msgid="4139212359561383385">"ลดจำนวนสีในภาพ"</string>
-    <string name="redeye" msgid="4958448806369928239">"ตาแดง"</string>
-    <string name="rotate" msgid="6607597269792373083">"หมุน"</string>
-    <string name="saturation" msgid="8621322012271169931">"ความอิ่มตัว"</string>
-    <string name="sepia" msgid="7978093531824705601">"ซีเปีย"</string>
-    <string name="shadow" msgid="8235188588101973090">"เงา"</string>
-    <string name="sharpen" msgid="8449662378104403230">"ทำให้ชัด"</string>
-    <string name="straighten" msgid="5217801513491493491">"ทำให้ตรง"</string>
-    <string name="temperature" msgid="1607987938521534517">"อบอุ่น"</string>
-    <string name="tint" msgid="154435943863418434">"แต้มสี"</string>
-    <string name="vignette" msgid="7648125924662648282">"วิกเน็ตต์"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"ลากเครื่องหมายเพื่อครอบตัดภาพ"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"วาดบนภาพเพื่อ doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"ลากภาพเพื่อพลิก"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"แตะที่ตาแดงเพื่อนำออก"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"ลากภาพเพื่อหมุน"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"ลากภาพเพื่อยืดให้ตรง"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"เอฟเฟ็กต์การเปิดรับแสง"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"เอฟเฟ็กต์สี"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"เอฟเฟ็กต์ด้านศิลปะ"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"ปรับแก้ไข"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"เลิกทำ"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"ทำซ้ำ"</string>
-</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 02e1bf8..3d4889b 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"หมุนไปทางขวา"</string>
     <string name="no_such_item" msgid="5315144556325243400">"ไม่พบรายการ"</string>
     <string name="edit" msgid="1502273844748580847">"แก้ไข"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"แก้ไขแบบง่าย"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"กำลังประมวลผลคำขอให้แคช"</string>
     <string name="caching_label" msgid="4521059045896269095">"กำลังแคช..."</string>
     <string name="crop_action" msgid="3427470284074377001">"ตัด"</string>
     <string name="trim_action" msgid="703098114452883524">"ตัดแต่ง"</string>
+    <string name="mute_action" msgid="5296241754753306251">"ปิดเสียง"</string>
     <string name="set_as" msgid="3636764710790507868">"ตั้งค่าเป็น"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"ไม่สามารถปิดเสียงวิดีโอ"</string>
     <string name="video_err" msgid="7003051631792271009">"ไม่สามารถเล่นวิดีโอ"</string>
     <string name="group_by_location" msgid="316641628989023253">"ตามตำแหน่ง"</string>
     <string name="group_by_time" msgid="9046168567717963573">"ตามเวลา"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"อัตโนมัติ"</string>
     <string name="flash_on" msgid="7891556231891837284">"แฟลชทำงาน"</string>
     <string name="flash_off" msgid="1445443413822680010">"ไม่เปิดแฟลช"</string>
+    <string name="unknown" msgid="3506693015896912952">"ไม่ทราบ"</string>
     <string name="ffx_original" msgid="372686331501281474">"ต้นฉบับ"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"วินเทจ"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"ทันใจ"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"รูปด่วน"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"ทำให้สีซีด"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"สีน้ำเงิน"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"ขาวดำ"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"ไม่มีที่จัดเก็บข้อมูลภายนอกที่สามารถใช้ได้"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"มุมมองฟิล์มภาพยนตร์"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"มุมมองตาราง"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"มุมมองแบบเต็มหน้าจอ"</string>
     <string name="trimming" msgid="9122385768369143997">"กำลังตัด"</string>
+    <string name="muting" msgid="5094925919589915324">"กำลังปิดเสียง"</string>
     <string name="please_wait" msgid="7296066089146487366">"โปรดรอสักครู่"</string>
-    <string name="save_into" msgid="4960537214388766062">"กำลังบันทึกวิดีโอที่ตัดลงในอัลบั้ม:"</string>
+    <string name="save_into" msgid="9155488424829609229">"กำลังบันทึกวิดีโอใน <xliff:g id="ALBUM_NAME">%1$s</xliff:g>…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"ไม่สามารถตัด : วิดีโอปลายทางสั้นเกินไป"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"กำลังแสดงภาพพาโนรามา"</string>
     <string name="save" msgid="613976532235060516">"บันทึก"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"กำลังสแกนเนื้อหา..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"สแกน %1$d รายการแล้ว"</item>
+    <item quantity="one" msgid="4340019444460561648">"สแกน %1$d รายการแล้ว"</item>
+    <item quantity="other" msgid="3138021473860555499">"สแกน %1$d รายการแล้ว"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"กำลังจัดเรียง..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"สแกนเสร็จแล้ว"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"กำลังนำเข้า..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"ไม่มีเนื้อหาสำหรับการนำเข้าในอุปกรณ์นี้"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"ไม่มีอุปกรณ์ MTP ที่เชื่อมต่อกัน"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"ข้อผิดพลาดกล้องถ่ายรูป"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"ไม่สามารถเชื่อมต่อกับกล้องถ่ายรูป"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"กล้องถ่ายรูปถูกปิดใช้งานเนื่องจากนโยบายด้านความปลอดภัย"</string>
+    <string name="camera_label" msgid="6346560772074764302">"กล้อง"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"กล้องวิดีโอ"</string>
+    <string name="wait" msgid="8600187532323801552">"โปรดรอสักครู่..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"ต่อเชื่อมที่จัดเก็บข้อมูล USB ก่อนใช้กล้องถ่ายรูป"</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"ใส่การ์ด SD ก่อนใช้กล้องถ่ายรูป"</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"กำลังเตรียมที่เก็บข้อมูล USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"กำลังเตรียมการ์ด SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"ไม่สามารถเข้าถึงที่เก็บข้อมูล USB"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"ไม่สามารถเข้าถึงการ์ด SD"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"ยกเลิก"</string>
+    <string name="review_ok" msgid="1156261588693116433">"เสร็จสิ้น"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"การบันทึกเป็นช่วงเวลา"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"เลือกกล้องถ่ายรูป"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"ย้อนกลับ"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"ด้านหน้า"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"ตำแหน่งจัดเก็บ"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"ตำแหน่ง"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"ตัวจับเวลาถอยหลัง"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 วินาที"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d วินาที"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"บี๊ปตอนนับถอยหลัง"</string>
+    <string name="setting_off" msgid="4480039384202951946">"ปิด"</string>
+    <string name="setting_on" msgid="8602246224465348901">"เปิด"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"คุณภาพวิดีโอ"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"สูง"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"ต่ำ"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"ช่วงเวลา"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"การตั้งค่ากล้องถ่ายรูป"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"การตั้งค่ากล้องวิดีโอ"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"ขนาดของภาพ"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M พิกเซล(16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 ล้านพิกเซล"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"โหมดโฟกัส"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"อัตโนมัติ"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"อินฟินิตี"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"มาโคร"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"อัตโนมัติ"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"อินฟินิตี"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"มาโคร"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"โหมดแฟลช"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"โหมดแฟลช"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"อัตโนมัติ"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"เปิด"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"ปิด"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"แฟลชอัตโนมัติ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"เปิดแฟลช"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"ปิดแฟลช"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"ไวต์บาลานซ์"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"ไวท์บาลานซ์"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"อัตโนมัติ"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"หลอดไส้"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"กลางวัน"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"ฟลูออเรสเซนต์"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"เมฆมาก"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"อัตโนมัติ"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"หลอดไส้"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"กลางวัน"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ฟลูออเรสเซนต์"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"เมฆมาก"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"โหมดสำเร็จรูป"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"อัตโนมัติ"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"การทำงาน"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"กลางคืน"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"ดวงอาทิตย์ตก"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"งานเลี้ยง"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"ไม่มี"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"การทำงาน"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"กลางคืน"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ดวงอาทิตย์ตก"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ปาร์ตี้"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ตัวจับเวลาถอยหลัง"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ตัวจับเวลาปิด"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 วินาที"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 วินาที"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 วินาที"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 วินาที"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"ไม่สามารถเลือกได้ในโหมดสำเร็จรูป"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"การรับแสง"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"การรับแสง"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"กล้องด้านหน้า"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"กล้องด้านหลัง"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"ตกลง"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"ที่เก็บข้อมูล USB ของคุณไม่มีพื้นที่เหลือ ให้เปลี่ยนการตั้งค่าคุณภาพหรือลบบางภาพหรือไฟล์อื่นๆ"</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"การ์ด SD ของคุณไม่มีพื้นที่เหลือ ให้เปลี่ยนการตั้งค่าคุณภาพหรือลบบางภาพหรือไฟล์อื่นๆ"</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"ขนาดถึงขีดจำกัดแล้ว"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"เร็วเกินไป"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"กำลังสร้างพาโนรามา"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"ไม่สามารถบันทึกภาพพาโนรามา"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"พาโนรามา"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"กำลังจับภาพพาโนรามา"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"กำลังรอภาพพาโนรามาก่อนหน้า"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"บันทึก..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"กำลังแสดงภาพพาโนรามา"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"แตะเพื่อโฟกัส"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"เอฟเฟ็กต์"</string>
+    <string name="effect_none" msgid="3601545724573307541">"ไม่มี"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"บีบ"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"ตาโต"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"ปากใหญ่"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"ปากจู๋"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"จมูกโต"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"ตาเล็ก"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"พื้นที่ว่าง"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"พระอาทิตย์ตก"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"วิดีโอของคุณ"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"พักอุปกรณ์ของคุณ"\n"ออกจากการแสดงผลเป็นเวลาสักครู่หนึ่ง"</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"แตะเพื่อถ่ายภาพในขณะบันทึก"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"เริ่มบันทึกวิดีโอแล้ว"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"หยุดบันทึกวิดีโอแล้ว"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"การจับภาพวิดีโอจะถูกปิดใช้งานเมื่อเปิดใช้เอฟเฟ็กต์พิเศษ"</string>
+    <string name="clear_effects" msgid="5485339175014139481">"ล้างเอฟเฟ็กต์"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"หน้าตลก"</string>
+    <string name="effect_background" msgid="6579360207378171022">"พื้นหลัง"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"ปุ่มชัตเตอร์"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"ปุ่มเมนู"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"ภาพล่าสุด"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"สลับระหว่างกล้องด้านหน้าและด้านหลัง"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"ตัวเลือกกล้องถ่ายรูป วิดีโอ หรือภาพพาโนรามา"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"การควบคุมการตั้งค่าเพิ่มเติม"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"ปิดการควบคุมการตั้งค่า"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"การควบคุมการย่อ/ขยาย"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"ลดลง %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"เพิ่มขึ้น %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"ช่องทำเครื่องหมาย %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"เปลี่ยนเป็นภาพถ่าย"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"เปลี่ยนเป็นวิดีโอ"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"เปลี่ยนเป็นภาพพาโนรามา"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"เปลี่ยนเป็นภาพพาโนรามาใหม่"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"ไม่ผ่านการตรวจสอบ"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"ผ่านการตรวจสอบ"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"ตรวจสอบการถ่ายภาพ/วิดีโอใหม่"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"เล่นวิดีโอ"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"หยุดวิดีโอชั่วคราว"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"โหลดวิดีโอซ้ำ"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"แถบเวลาของโปรแกรมเล่นวิดีโอ"</string>
+    <string name="capital_on" msgid="5491353494964003567">"เปิด"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ปิด"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"ปิด"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 วินาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 นาที"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 ชั่วโมง"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 ชั่วโมง"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"วินาที"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"นาที"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"ชั่วโมง"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"เสร็จสิ้น"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"ตั้งค่าช่วงเวลา"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"คุณลักษณะช่วงเวลาปิดอยู่ เปิดคุณลักษณะนี้เพื่อตั้งค่าช่วงเวลา"</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"ตัวจับเวลาถอยหลังปิดอยู่ เปิดตัวจับเวลาเพื่อนับถอยหลังก่อนถ่ายภาพ"</string>
+    <string name="set_duration" msgid="5578035312407161304">"ตั้งระยะเวลาเป็นวินาที"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"นับถอยหลังเพื่อถ่ายภาพ"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"จดจำตำแหน่งภาพหรือไม่"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"แท็กรูปภาพและวิดีโอด้วยตำแหน่งที่ถ่ายภาพ"\n\n"แอปพลิเคชันอื่นๆ สามารถเข้าถึงข้อมูลนี้ตลอดจนภาพที่บันทึกไว้ของคุณได้"</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"ไม่ ขอบคุณ"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"ใช่"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"กล้องถ่ายรูป"</string>
+    <string name="menu_search" msgid="7580008232297437190">"ค้นหา"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"รูปภาพ"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"อัลบั้ม"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ตัวเลือกเพิ่มเติม"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"การตั้งค่า"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d ภาพ"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d ภาพ"</item>
+  </plurals>
 </resources>
diff --git a/res/values-tl/filtershow_strings.xml b/res/values-tl/filtershow_strings.xml
index 4b96f8e..e0d5b10 100644
--- a/res/values-tl/filtershow_strings.xml
+++ b/res/values-tl/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Hindi ma-load ang larawan!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Itinatakda ang wallpaper"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Hindi ma-download ang larawan. Hindi available ang network."</string>
     <string name="original" msgid="3524493791230430897">"Orihinal"</string>
     <string name="borders" msgid="2067345080568684614">"Mga Border"</string>
-    <string name="done" msgid="3112344807927554662">"Tapos na"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"I-undo"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"I-redo"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Ipakita Kasaysayan"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Kasaysayan Pagtago"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Ipakita Image State"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Itago Image State"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Ipakita Mga Inilapat na Effect"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Itago Mga Inilapat na Effect"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Mga Setting"</string>
+    <string name="unsaved" msgid="8704442449002374375">"May mga hindi naka-save na pagbabago sa larawang ito."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Gusto mo bang mag-save bago lumabas?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"I-save at Lumabas"</string>
+    <string name="exit" msgid="242642957038770113">"Lumabas"</string>
     <string name="history" msgid="455767361472692409">"Kasaysayan"</string>
     <string name="reset" msgid="9013181350779592937">"I-reset"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Kasalukuyang Katayuan ng Larawan"</string>
+    <string name="imageState" msgid="8632586742752891968">"Mga Nakalapat na Effect"</string>
     <string name="compare_original" msgid="8140838959007796977">"Ihambing"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Ilapat"</string>
     <string name="reset_effect" msgid="7712605581024929564">"I-reset"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Wala"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Nakapirmi"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Tiny Planet"</string>
     <string name="exposure" msgid="6526397045949374905">"Exposure"</string>
     <string name="sharpness" msgid="6463103068318055412">"Sharpness"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Mga Shadow"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Mga Highlight"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Mga Kurba"</string>
     <string name="vignette" msgid="934721068851885390">"Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Red Eye"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Gumuhit"</string>
     <string name="straighten" msgid="26025591664983528">"Ituwid"</string>
     <string name="crop" msgid="5781263790107850771">"I-crop"</string>
     <string name="rotate" msgid="2796802553793795371">"I-rotate"</string>
     <string name="mirror" msgid="5482518108154883096">"Mirror"</string>
+    <string name="negative" msgid="6998313764388022201">"Negative"</string>
     <string name="none" msgid="6633966646410296520">"Wala"</string>
+    <string name="edge" msgid="7036064886242147551">"Mga Gilid"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Downsample"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Pula"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Berde"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Asul"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Estilo"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Laki"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Kulay"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Mga Linya"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Marker"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Spatter"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"I-clear"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Pumili ng custom na kulay"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Pumili ng Kulay"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Pumili ng Laki"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Orihinal"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Resulta"</string>
 </resources>
diff --git a/res/values-tl/photoeditor_strings.xml b/res/values-tl/photoeditor_strings.xml
deleted file mode 100644
index 8614c77..0000000
--- a/res/values-tl/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Hindi mai-load ang larawan"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Hindi mai-save ang na-edit na larawan"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Nai-save ang na-edit na larawan sa <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"I-discard ang di naka-save na pagbabago?"</string>
-    <string name="yes" msgid="5402582493291792293">"Oo"</string>
-    <string name="save" msgid="5516670392524294967">"I-SAVE"</string>
-    <string name="autofix" msgid="1663414996270538748">"Auto-fix"</string>
-    <string name="crop" msgid="7598378507763334041">"Pag-crop"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"I-cross-process"</string>
-    <string name="documentary" msgid="50396326708699797">"Dokumentaryo"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Duo-tone"</string>
-    <string name="facelift" msgid="6205748523156172637">"Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Pampunong Ilaw"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Fisheye"</string>
-    <string name="flip" msgid="2357692401826287480">"Baliktarin"</string>
-    <string name="grain" msgid="7487585304579789098">"Film Grain"</string>
-    <string name="grayscale" msgid="7641563843060945228">"B&amp;W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Black and white"</string>
-    <string name="highlight" msgid="3902653944386623972">"Mga Highlight"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negative"</string>
-    <string name="posterize" msgid="4139212359561383385">"I-posterize"</string>
-    <string name="redeye" msgid="4958448806369928239">"Pulang Mata"</string>
-    <string name="rotate" msgid="6607597269792373083">"Paikutin"</string>
-    <string name="saturation" msgid="8621322012271169931">"Saturation"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Mga Shadow"</string>
-    <string name="sharpen" msgid="8449662378104403230">"I-sharpen"</string>
-    <string name="straighten" msgid="5217801513491493491">"Ituwid"</string>
-    <string name="temperature" msgid="1607987938521534517">"Warmth"</string>
-    <string name="tint" msgid="154435943863418434">"Tint"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vignette"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"I-drag ang mga marker upang i-crop ang larawan"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Gumuhit sa larawan upang mag-doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"I-drag ang larawan upang i-flip ito"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Pindutin ang mga red eye upang alisin ang mga ito"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"I-drag ang larawan upang i-rotate ito"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"I-drag ang larawan upang iunat ito"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Mga exposure effect"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Mga color effect"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Mga artistic effect"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Fix up"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"I-undo"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Gawing muli"</string>
-</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 3c6b6a7..ca5dfb8 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"I-rotate pakanan"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Hindi mahanap ang item."</string>
     <string name="edit" msgid="1502273844748580847">"I-edit"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Simpleng Pag-edit"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Pinoproseso ang mga kahilingan sa pag-cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Nagka-cache…"</string>
     <string name="crop_action" msgid="3427470284074377001">"I-crop"</string>
     <string name="trim_action" msgid="703098114452883524">"Trim"</string>
+    <string name="mute_action" msgid="5296241754753306251">"I-mute"</string>
     <string name="set_as" msgid="3636764710790507868">"Itakda bilang"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Hindi ma-mute ang video."</string>
     <string name="video_err" msgid="7003051631792271009">"Hindi ma-play ang video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Ayon sa lokasyon"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Ayon sa oras"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Auto"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flash fired"</string>
     <string name="flash_off" msgid="1445443413822680010">"Walang flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Hindi alam"</string>
     <string name="ffx_original" msgid="372686331501281474">"Orihinal"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Walang available na panlabas na storage"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Filmstrip view"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Grid view"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Fullscreen na view"</string>
     <string name="trimming" msgid="9122385768369143997">"Pag-trim"</string>
+    <string name="muting" msgid="5094925919589915324">"Minu-mute"</string>
     <string name="please_wait" msgid="7296066089146487366">"Mangyaring maghintay"</string>
-    <string name="save_into" msgid="4960537214388766062">"Sine-save ang na-trim na video sa album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Sine-save ang video sa <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Hindi ma-trim : masyadong maikli ang target na video"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Nire-render ang panorama"</string>
     <string name="save" msgid="613976532235060516">"I-save"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Inii-scan ang nilalaman..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d (na) item ang na-scan"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d (na) item ang na-scan"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d (na) item ang na-scan"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Pinagbubukud-bukod..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Tapos na ang pag-scan"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Ini-import..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Walang nilalamang available para sa pag-import sa device na ito."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Walang MTP device na nakakonekta"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Error sa kamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Hindi makakonekta sa camera."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Hindi na pinagana ang camera dahil sa mga patakaran sa seguridad."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Camera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Camcorder"</string>
+    <string name="wait" msgid="8600187532323801552">"Pakihintay…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"I-mount ang USB storage bago gamitin ang camera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Magpasok ng isang SD card bago gamitin ang camera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Ihinahanda imbakan na USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Naghahanda ng SD card..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Hindi ma-access ang USB storage."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Hindi ma-access ang SD card."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"KANSELAHIN"</string>
+    <string name="review_ok" msgid="1156261588693116433">"TAPOS NA"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Pagre-record ng paglipas ng oras"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Pumili ng camera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Bumalik"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Harap"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Iimbak ang lokasyon"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"LOKASYON"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Timer ng countdown"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 segundo"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d (na) segundo"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Beep sa pag-countdown"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Naka-off"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Naka-on"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Kalidad ng video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Mataas"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Mababa"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Lumipas na oras"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Mga setting ng kamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Mga setting ng camcorder"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Laki ng larawan"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixels"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M pixels"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M pixels"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M pixels"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M pixels"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M pixel (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M pixels"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M pixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Focus mode"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Auto"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Walang Katapusan"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"AUTO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"INFINITY"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flash mode"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"FLASH MODE"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Auto"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Naka-on"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Naka-off"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH AUTO"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"NAKA-ON ANG FLASH"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"NAKA-OFF ANG FLASH"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"White balance"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"WHITE BALANCE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Auto"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Napakaliwanag"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Liwanag ng araw"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Fluorescent"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Maulap"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"AUTO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"INCANDESCENT"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"DAYLIGHT"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLUORESCENT"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"CLOUDY"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Scene mode"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Auto"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Pagkilos"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Gabi"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Paglubog ng araw"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Partido"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"WALA"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ACTION"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"NIGHT"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"SUNSET"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PARTY"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"COUNTDOWN TIMER"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"NAKA-OFF ANG TIMER"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SEGUNDO"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SEGUNDO"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Hindi mapipili sa mode ng scene."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Pagkakalantad"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"EXPOSURE"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"CAMERA SA HARAP"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"CAMERA SA LIKOD"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Nauubusan na ng puwang ang iyong imbakan na USB. Baguhin ang setting ng kalidad o tanggalin ang ilan sa mga larawan o ibang mga file."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Nauubusan na ng puwang ang SD card. Baguhin ang setting ng kalidad o tanggalin ang ilan sa mga larawan o ibang mga file."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Naabot ang limitasyon ng laki."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Masyadong mabilis"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Ihinahanda ang panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Hindi ma-save ang panorama."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Kinukunan ang panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Hinihintay ang nakaraang panorama"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Sine-save…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Nire-render ang panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Pindutin upang tumuon."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Mga Effect"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Wala"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Squeeze"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Malalaking mata"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Malaking bibig"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Maliit na bibig"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Malaking ilong"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Maliliit na mata"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Sa kalawakan"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Paglubog ng araw"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Iyong video"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Huwag munang gamitin ang iyong device."\n"Umalis muna nang ilang sandali."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Pindutin upang kumuha ng larawan habang nagre-record."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Nagsimula na ang pag-record ng video."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Tumigil ang pag-record ng video."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Hindi pinapagana ang snapshot sa video kapag naka-on ang mga espesyal na effect."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Mga clear na effect"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"MGA KATAWA-TAWANG MUKHA"</string>
+    <string name="effect_background" msgid="6579360207378171022">"BACKGROUND"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Button ng shutter"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Button ng menu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Pinakakamakailang larawan"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Switch ng camera sa harap at likod"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Tagapili ng camera, video, o panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Higit pang mga kontrol ng setting"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Isara ang mga kontrol ng setting"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Kontrol ng pag-zoom"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Bawasan ang %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Dagdagan ang %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"check box ng %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Lumipat sa larawan"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Lumipat sa video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Lumipat sa panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Lumipat sa bagong panorama"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Suriin ang pagkansela"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Tapos na ang pagsusuri"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Suriin ang muling pagkuha"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"I-play ang video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"I-pause ang video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"I-reload ang video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Bar ng oras ng video player"</string>
+    <string name="capital_on" msgid="5491353494964003567">"I-ON"</string>
+    <string name="capital_off" msgid="7231052688467970897">"I-OFF"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Pag-off"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 na segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 na segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 na segundo"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 na minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 na minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 na minuto"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 na oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 na oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 oras"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 na oras"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"mga segundo"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"mga minuto"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"mga oras"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Tapos na"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Itakda ang Palugit ng Oras"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Naka-off ang tampok sa lumipas na oras. I-on ito upang itakda ang palugit ng oras."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Naka-off ang timer ng countdown. I-on ito upang magbilang bago kumuha ng larawan."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Itakda ang tagal sa loob ng ilang segundo"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Nagbibilang upang kumuha ng larawan"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Tandaan ang mga lokasyon ng larawan?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"I-tag ang iyong mga larawan at video sa mga lokasyon kung saan kinunan ang mga iyon."\n\n"Maaaring i-access ng mga ibang app ang impormasyong ito kasama ng iyong mga na-save na larawan."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Hindi na, salamat"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Oo"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Camera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Maghanap"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Mga Larawan"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Mga Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"HIGIT PANG MGA PAGPIPILIAN"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"MGA SETTING"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d larawan"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d larawan"</item>
+  </plurals>
 </resources>
diff --git a/res/values-tr/filtershow_strings.xml b/res/values-tr/filtershow_strings.xml
index 2a785de..585e789 100644
--- a/res/values-tr/filtershow_strings.xml
+++ b/res/values-tr/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Resim yüklenemiyor"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Duvar kağıdı ayarlanıyor"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Fotoğraf indirilemedi. Ağ kullanılamıyor."</string>
     <string name="original" msgid="3524493791230430897">"Orijinal"</string>
     <string name="borders" msgid="2067345080568684614">"Kenarlıklar"</string>
-    <string name="done" msgid="3112344807927554662">"Bitti"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Geri al"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Yeniden yap"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Geçmişi Göster"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Geçmişi Gizle"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Resim Durumunu Göster"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Resim Durumunu Gizle"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Uygulanan Efektleri Göster"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Uygulanan Efektleri Gizle"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Ayarlar"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Bu resimde kaydedilmemiş değişiklikler var."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Çıkmadan önce kaydetmek ister misiniz?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Kaydet ve Çık"</string>
+    <string name="exit" msgid="242642957038770113">"Çıkış"</string>
     <string name="history" msgid="455767361472692409">"Geçmiş"</string>
     <string name="reset" msgid="9013181350779592937">"Sıfırla"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Mevcut Resim Durumu"</string>
+    <string name="imageState" msgid="8632586742752891968">"Uygulanan Efektler"</string>
     <string name="compare_original" msgid="8140838959007796977">"Karşılaştır"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Uygula"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Sıfırla"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Hiçbiri"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Sabit"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Küçük Gezegen"</string>
     <string name="exposure" msgid="6526397045949374905">"Pozlama"</string>
     <string name="sharpness" msgid="6463103068318055412">"Keskinlik"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Otomatik Renk"</string>
     <string name="hue" msgid="6231252147971086030">"Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Gölgeler"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Parlak Noktalar"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Eğriler"</string>
     <string name="vignette" msgid="934721068851885390">"Vinyet"</string>
     <string name="redeye" msgid="4508883127049472069">"Kırmızı Göz"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Çiz"</string>
     <string name="straighten" msgid="26025591664983528">"Düzleştir"</string>
     <string name="crop" msgid="5781263790107850771">"Kırp"</string>
     <string name="rotate" msgid="2796802553793795371">"Döndür"</string>
     <string name="mirror" msgid="5482518108154883096">"Ayna"</string>
+    <string name="negative" msgid="6998313764388022201">"Negatif"</string>
     <string name="none" msgid="6633966646410296520">"Hiçbiri"</string>
+    <string name="edge" msgid="7036064886242147551">"Kenarlar"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Küçült"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Kırmızı"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Yeşil"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Mavi"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Stil"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Boyut"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Renk"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Çizgi"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Fosforlu kalem"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Serpme aracı"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Temizle"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Özel renk seç"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Renk Seçin"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Boyut Seçin"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"Tamam"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Orijinal"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Sonuç"</string>
 </resources>
diff --git a/res/values-tr/photoeditor_strings.xml b/res/values-tr/photoeditor_strings.xml
deleted file mode 100644
index d7fe890..0000000
--- a/res/values-tr/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Fotoğraf Stüdyosu"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Fotoğraf yüklenemedi"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Düzenlenen fotoğraf kaydedilemedi"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Düzenlenen fotoğraf <xliff:g id="FOLDER_NAME">%s</xliff:g> içine kaydedildi"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Kaydedilmeyen değişiklikler silinsin mi?"</string>
-    <string name="yes" msgid="5402582493291792293">"Evet"</string>
-    <string name="save" msgid="5516670392524294967">"KAYDET"</string>
-    <string name="autofix" msgid="1663414996270538748">"Otomatik düzelt"</string>
-    <string name="crop" msgid="7598378507763334041">"Kırp"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Çapraz Banyo"</string>
-    <string name="documentary" msgid="50396326708699797">"Belgesel"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"İki tonlu"</string>
-    <string name="facelift" msgid="6205748523156172637">"Yüz Gerdirme"</string>
-    <string name="facetan" msgid="4412831806626044111">"Yanık Tenli Yüz"</string>
-    <string name="filllight" msgid="2644989991700022526">"Dolgu Işığı"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Balıkgözü"</string>
-    <string name="flip" msgid="2357692401826287480">"Çevir"</string>
-    <string name="grain" msgid="7487585304579789098">"Film Greni"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Siyah Beyaz"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Siyah beyaz"</string>
-    <string name="highlight" msgid="3902653944386623972">"Parlak noktalar"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Negatif"</string>
-    <string name="posterize" msgid="4139212359561383385">"Poster Efkt Uyg"</string>
-    <string name="redeye" msgid="4958448806369928239">"Kırmızı Göz"</string>
-    <string name="rotate" msgid="6607597269792373083">"Döndür"</string>
-    <string name="saturation" msgid="8621322012271169931">"Doygunluk"</string>
-    <string name="sepia" msgid="7978093531824705601">"Sepya Tonu"</string>
-    <string name="shadow" msgid="8235188588101973090">"Gölgeler"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Keskinleştir"</string>
-    <string name="straighten" msgid="5217801513491493491">"Düzleştir"</string>
-    <string name="temperature" msgid="1607987938521534517">"Sıcaklık"</string>
-    <string name="tint" msgid="154435943863418434">"Tonlama"</string>
-    <string name="vignette" msgid="7648125924662648282">"Vinyet"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Fotoğrafı kırpmak içni işaretçileri sürükleyin"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Doodle için fotoğrafın üzerinde çizin"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Çevirmek için fotoğrafı sürükleyin"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Kırmızı gözleri hafifçe vurarak düzeltin"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Döndürmek için fotoğrafı sürükleyin"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Düzleştirmek için fotoğrafı sürükleyin"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Pozlama efektleri"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Renk efektleri"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Sanatsal efektler"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Düzelt"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Geri al"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Yeniden Yap"</string>
-</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index bf5c624..6e11663 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Sağa döndür"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Öğe bulunamadı."</string>
     <string name="edit" msgid="1502273844748580847">"Düzenle"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Basit Düzenleme"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Önbelleğe alma istekleri işleniyor"</string>
     <string name="caching_label" msgid="4521059045896269095">"Önbelleğe alınıyor..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Kırp"</string>
     <string name="trim_action" msgid="703098114452883524">"Kırp"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Sesi kapat"</string>
     <string name="set_as" msgid="3636764710790507868">"Şu şekilde ayarla:"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Videonun sesi kapatılamıyor."</string>
     <string name="video_err" msgid="7003051631792271009">"Video oynatılamıyor."</string>
     <string name="group_by_location" msgid="316641628989023253">"Konuma göre"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Tarihe göre"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"Otomatik"</string>
     <string name="flash_on" msgid="7891556231891837284">"Flaş patladı"</string>
     <string name="flash_off" msgid="1445443413822680010">"Flaş yok"</string>
+    <string name="unknown" msgid="3506693015896912952">"Bilinmiyor"</string>
     <string name="ffx_original" msgid="372686331501281474">"Orijinal"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Klasik"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"Anlık"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"Şipşak"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"Beyazlatma"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"Mavi"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"Siyah Beyaz"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"Kullanılabilir harici depolama yok"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Film şeridi görünümü"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Tablo görünümü"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Tam ekran görünümü"</string>
     <string name="trimming" msgid="9122385768369143997">"Kırpma"</string>
+    <string name="muting" msgid="5094925919589915324">"Ses kapatılıyor"</string>
     <string name="please_wait" msgid="7296066089146487366">"Lütfen bekleyin"</string>
-    <string name="save_into" msgid="4960537214388766062">"Kırpılan video albüme kaydediliyor:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Video <xliff:g id="ALBUM_NAME">%1$s</xliff:g> adlı albüme kaydediliyor…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Kırpılamaz: hedef video çok kısa"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Panorama oluşturuluyor"</string>
     <string name="save" msgid="613976532235060516">"Kaydet"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"İçerik taranıyor..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d öğe tarandı"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d öğe tarandı"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d öğe tarandı"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Sıralanıyor..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Tarama tamamlandı"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"İçe aktarılıyor..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Bu cihazda içe aktarılacak içerik yok."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Bağlı MTP cihazı yok"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Kamera hatası"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Kameraya bağlanılamıyor."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Kamera, güvenlik politikaları nedeniyle devre dışı bırakıldı."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Kamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Video Kamera"</string>
+    <string name="wait" msgid="8600187532323801552">"Lütfen bekleyin..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Kamerayı kullanmadan önce USB bellek ekleyin."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Kamerayı kullanmadan önce bir SD kart takın."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"USB bellek hazırlanıyor…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"SD kart hazırlanıyor..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"USB belleğe erişilemedi."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"SD karta erişilemedi."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"İPTAL"</string>
+    <string name="review_ok" msgid="1156261588693116433">"BİTTİ"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Zaman atlamalı kayıt"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Kamera seç"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Arka"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Ön"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Depo konumu"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"KONUM"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Zamanlayıcı"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 saniye"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d saniye"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Geri sayım sırasında bip sesi"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Kapalı"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Açık"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Video kalitesi"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Yüksek"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Düşük"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Zaman atlama"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Kamera ayarları"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Kamera ayarları"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Resim boyutu"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 megapiksel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 M piksel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M piksel"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M piksel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M piksel"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3M piksel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M piksel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Odak modu"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Otomatik"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Sonsuz"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Makro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"OTOMATİK"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"SONSUZ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MAKRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Flaş modu"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"FLAŞ MODU"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Otomatik"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Açık"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Kapalı"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"OTOMATİK FLAŞ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"FLAŞ AÇIK"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"FLAŞ KAPALI"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Beyaz dengesi"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"BEYAZ DENGESİ"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Otomatik"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Ampul"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Gün Işığı"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Floresan"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Bulutlu"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"OTOMATİK"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"AMPUL"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"GÜN IŞIĞI"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"FLORESAN"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"BULUTLU"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Sahne modu"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Otomatik"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"İşlem"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Gece"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Gün batımı"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Parti"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"YOK"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"AKSİYON"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"GECE"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"GÜN BATIMI"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"PARTİ"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"GERİ SAYIM ZAMANLAYICI"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ZAMANLAYICI KAPALI"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 SANİYE"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 SANİYE"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 SANİYE"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 SANİYE"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Sahne modunda seçilemez."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Pozlama"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"POZLAMA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"ÖN KAMERA"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"ARKA KAMERA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"Tamam"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB belleğinizde boş alan azaldı. Kalite ayarını değiştirin veya bazı resimleri ya da diğer dosyaları silin."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD kartınızda boş alan azaldı. Kalite ayarını değiştirin veya bazı resimleri ya da diğer dosyaları silin."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Boyut sınırına ulaşıldı."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Çok hızlı"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Panorama hazırlanıyor"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Panorama kaydedilemedi."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Panorama kaydediliyor"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Önceki panorama bekleniyor"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Kaydediliyor..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Panorama oluşturuluyor"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Odaklamak için dokunun."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Efektler"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Yok"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Sıkıştır"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Büyük gözler"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Büyük ağız"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Küçük ağız"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Büyük burun"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Küçük gözler"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Uzayda"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Gün Batımı"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Videonuz"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Cihazınızı yerleştirin."\n"Kısa bir süre için görüntüden çıkın."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Kayıt sırasında fotoğraf çekmek için dokunun."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Video kaydı başladı."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Video kaydı durdu."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Özel efektler açıkken video anlık görüntü yakalama devre dışıdır."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Efektleri temizle"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"KOMİK SURATLAR"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ARKA PLAN"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Deklanşör düğmesi"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Menü düğmesi"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"En son fotoğraf"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Ön/arka kamera anahtarı"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Kamera, video veya panorama seçici"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Diğer ayar denetimleri"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Ayar denetimlerini kapat"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Zum denetimi"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"%1$s azalt"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"%1$s artır"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s onay kutusu"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Fotoğrafa geçiş yap"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Videoya geç"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Panorama moduna geç"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Yeni panoramaya geç"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Yorumu iptal et"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Yorum tamamlandı"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"İncelemede tekrar çek"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Videoyu oynat"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Videoyu duraklat"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Videoyu yeniden yükle"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Video oynatıcı zaman çubuğu"</string>
+    <string name="capital_on" msgid="5491353494964003567">"AÇ"</string>
+    <string name="capital_off" msgid="7231052688467970897">"KAPAT"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Kapalı"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 saniye"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 dakika"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 saat"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 saat"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"saniye"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"dakika"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"saat"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Bitti"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Zaman Aralığını Ayarla"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Zaman atlama özelliği kapalı. Zaman aralığını ayarlamak için etkinleştirin."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Zamanlayıcı kapalı. Fotoğraf çekmeden önce geri saymak için açın."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Süreyi saniye cinsinden ayarlayın"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Fotoğraf çekmek için geri sayılıyor"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Fotoğrafların çekildiği yerler hatırlansın mı?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Fotoğraflarınızı ve videolarınızı çekildikleri konumlarla etiketleyin."\n\n"Diğer uygulamalar, kaydedilen görüntülerle birlikte bu bilgilere erişebilir."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Hayır, teşekkürler"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Evet"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Kamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Ara"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Fotoğraflar"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Albümler"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"DİĞER SEÇENEKLER"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"AYARLAR"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d fotoğraf"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d fotoğraf"</item>
+  </plurals>
 </resources>
diff --git a/res/values-uk/filtershow_strings.xml b/res/values-uk/filtershow_strings.xml
index c3a085b..dcc2098 100644
--- a/res/values-uk/filtershow_strings.xml
+++ b/res/values-uk/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Неможливо завантажити зображення."</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Встановлення фонового малюнка"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Не вдалося завантажити фотографію. Мережа не доступна."</string>
     <string name="original" msgid="3524493791230430897">"Оригінал"</string>
     <string name="borders" msgid="2067345080568684614">"Облямівка"</string>
-    <string name="done" msgid="3112344807927554662">"Готово"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Відмінити"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Повторити"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Показати історію"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Сховати історію"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Показ. стан зображ."</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Сховати стан зображ."</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Показати застосовані ефекти"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Сховати застосовані ефекти"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Налаштування"</string>
+    <string name="unsaved" msgid="8704442449002374375">"У зображенні є незбережені зміни."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Зберегти перед виходом?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Зберегти та вийти"</string>
+    <string name="exit" msgid="242642957038770113">"Вийти"</string>
     <string name="history" msgid="455767361472692409">"Історія"</string>
     <string name="reset" msgid="9013181350779592937">"Скинути"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Поточний стан зображення"</string>
+    <string name="imageState" msgid="8632586742752891968">"Застосовані ефекти"</string>
     <string name="compare_original" msgid="8140838959007796977">"Порівняти"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Застосувати"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Скинути"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Немає"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Зафіксовано"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Мала планета"</string>
     <string name="exposure" msgid="6526397045949374905">"Експозиція"</string>
     <string name="sharpness" msgid="6463103068318055412">"Різкість"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Автоколір"</string>
     <string name="hue" msgid="6231252147971086030">"Тон"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Тіні"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Затемнення"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Криві"</string>
     <string name="vignette" msgid="934721068851885390">"Віньєтка"</string>
     <string name="redeye" msgid="4508883127049472069">"Червоні очі"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Малювати"</string>
     <string name="straighten" msgid="26025591664983528">"Вирівнювання"</string>
     <string name="crop" msgid="5781263790107850771">"Обрізати"</string>
     <string name="rotate" msgid="2796802553793795371">"Обертання"</string>
     <string name="mirror" msgid="5482518108154883096">"Дзеркало"</string>
+    <string name="negative" msgid="6998313764388022201">"Негатив"</string>
     <string name="none" msgid="6633966646410296520">"Нічого"</string>
+    <string name="edge" msgid="7036064886242147551">"Краї"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Ворхол"</string>
+    <string name="downsample" msgid="3552938534146980104">"Зниж.якість"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Червоний"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Зелений"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Синій"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Стиль"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Розмір"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Колір"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Лінії"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Маркер"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Розпилення"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Очистити"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Вибрати спеціальний колір"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Вибрати колір"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Вибрати розмір"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"ОК"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Оригінал"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Результат"</string>
 </resources>
diff --git a/res/values-uk/photoeditor_strings.xml b/res/values-uk/photoeditor_strings.xml
deleted file mode 100644
index 893dfd4..0000000
--- a/res/values-uk/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Photo Studio"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Не вдалося завантажити фотографію"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Не вдалося зберегти відредаговане фото"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Відредаговане фото збережено в папці <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Відхилити незбережені зміни?"</string>
-    <string name="yes" msgid="5402582493291792293">"Так"</string>
-    <string name="save" msgid="5516670392524294967">"ЗБЕРЕГТИ"</string>
-    <string name="autofix" msgid="1663414996270538748">"Автовиправлення"</string>
-    <string name="crop" msgid="7598378507763334041">"Обрізка"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Крос-процес"</string>
-    <string name="documentary" msgid="50396326708699797">"Чорно-білі тони"</string>
-    <string name="doodle" msgid="1686409894518940990">"Doodle"</string>
-    <string name="duotone" msgid="8145893940788467106">"Два тони"</string>
-    <string name="facelift" msgid="6205748523156172637">"Світло на обличчі"</string>
-    <string name="facetan" msgid="4412831806626044111">"Засмага обличчя"</string>
-    <string name="filllight" msgid="2644989991700022526">"Освітлення"</string>
-    <string name="fisheye" msgid="6037488646928998921">"\"Риб’яче око\""</string>
-    <string name="flip" msgid="2357692401826287480">"Перевернути"</string>
-    <string name="grain" msgid="7487585304579789098">"Зернистість плівки"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Чорно-білий"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Чорно-білі"</string>
-    <string name="highlight" msgid="3902653944386623972">"Світлові ефекти"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Ломо"</string>
-    <string name="negative" msgid="1985508917342811252">"Негатив"</string>
-    <string name="posterize" msgid="4139212359561383385">"Ефект плаката"</string>
-    <string name="redeye" msgid="4958448806369928239">"Червоні очі"</string>
-    <string name="rotate" msgid="6607597269792373083">"Обертання"</string>
-    <string name="saturation" msgid="8621322012271169931">"Насиченість"</string>
-    <string name="sepia" msgid="7978093531824705601">"Сепія"</string>
-    <string name="shadow" msgid="8235188588101973090">"Тіні"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Різкість"</string>
-    <string name="straighten" msgid="5217801513491493491">"Вирівняти"</string>
-    <string name="temperature" msgid="1607987938521534517">"Тепло"</string>
-    <string name="tint" msgid="154435943863418434">"Відтінок"</string>
-    <string name="vignette" msgid="7648125924662648282">"Віньєтка"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Перетягніть маркери, щоб обрізати фото"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Малюйте на фото, щоб створити doodle"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Потягніть за фото, щоб перевернути його"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Торкн. червоних очей, щоб усунути ефект"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Потягніть за фото, щоб обертати його"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Перетягніть фото, щоб вирівняти його"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Ефекти експозиції"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Кольорові ефекти"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Художні ефекти"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Виправлення"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Відмінити"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Повторити"</string>
-</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 6d1af8e..f894853 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Поверн. вправо"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Не вдалося знайти елемент."</string>
     <string name="edit" msgid="1502273844748580847">"Редагувати"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Просте редагування"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Виконується обробка запитів кешування"</string>
     <string name="caching_label" msgid="4521059045896269095">"Кешування..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Обрізати"</string>
     <string name="trim_action" msgid="703098114452883524">"Обрізати"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Вимкнути звук"</string>
     <string name="set_as" msgid="3636764710790507868">"Устан. як"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Неможливо вимкнути звук."</string>
     <string name="video_err" msgid="7003051631792271009">"Неможливо відтворити відео."</string>
     <string name="group_by_location" msgid="316641628989023253">"За місцезнаходженням"</string>
     <string name="group_by_time" msgid="9046168567717963573">"За часом"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Автомат."</string>
     <string name="flash_on" msgid="7891556231891837284">"Викор. спалах"</string>
     <string name="flash_off" msgid="1445443413822680010">"Без спалаху"</string>
+    <string name="unknown" msgid="3506693015896912952">"Невідомо"</string>
     <string name="ffx_original" msgid="372686331501281474">"Оригінал"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Ретро"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Миттєво"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Доступної зовнішньої пам’яті немає"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Діафільм"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Ескізи"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"На весь екран"</string>
     <string name="trimming" msgid="9122385768369143997">"Обрізання"</string>
+    <string name="muting" msgid="5094925919589915324">"Вимкнення звуку"</string>
     <string name="please_wait" msgid="7296066089146487366">"Зачекайте"</string>
-    <string name="save_into" msgid="4960537214388766062">"Збереження обрізаного відео в альбомі:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Зберігання відео в альбом <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Неможливо обрізати: цільове відео закоротке"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Обробка панорами"</string>
     <string name="save" msgid="613976532235060516">"Зберегти"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Сканування вмісту..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"Проскановано елементів: %1$d"</item>
+    <item quantity="one" msgid="4340019444460561648">"Проскановано елементів: %1$d"</item>
+    <item quantity="other" msgid="3138021473860555499">"Проскановано елементів: %1$d"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Сортування..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Сканування завершено"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Імпортування..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Немає вмісту для імпортування на цей пристрій."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Немає жодного під’єднаного носія MTP"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Помилка камери"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Неможливо підключитися до камери."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Камеру вимкнено відповідно до правил безпеки."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Камера"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Відеокамера"</string>
+    <string name="wait" msgid="8600187532323801552">"Зачекайте…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Підключіть носій USB перед тим, як користуватися камерою."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Вставте карту SD перед тим, як користуватися камерою."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Підготовка носія USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Підготовка карти SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Не вдалося отримати доступ до носія USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Не вдалося отримати доступ до карти SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"СКАСУВАТИ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"ГОТОВО"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Запис уповільненої зйомки"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Вибрати камеру"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Задня камера"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Передня камера"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Геотеги"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"МІСЦЕЗНАХОДЖЕННЯ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Таймер зворотного відліку"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 с"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d с"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Сигнал під час відліку"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Вимкнено"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Увімкнено"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Якість відео"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Висока"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Низька"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Уповільнена зйомка"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Налаштування камери"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Налашт-ня відеокамери"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Розмір фото"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13 Мпікс."</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8 мегапікселів"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5 Мпікс"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4 Мпікс."</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3 Мпікс"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2 Мпікс"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2 Мпікс. (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3 Mпікс"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1 Мпікс"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Режим фокусу"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Автом."</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Безкінечн."</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Макро"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"АВТО"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"БЕЗКІНЕЧНІСТЬ"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"МАКРО"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Режим спалаху"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"РЕЖИМ СПАЛАХУ"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Автом."</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Увімк."</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Вимк."</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"АВТОМАТИЧНИЙ СПАЛАХ"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"СПАЛАХ УВІМК."</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"СПАЛАХ ВИМК."</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Баланс білого"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"БАЛАНС БІЛОГО"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Автом."</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Лампа розжар."</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Сонячно"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Флуоресцентний"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Хмарно"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"АВТО"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ЛАМПА РОЗЖАРЮВАННЯ"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"СОНЯЧНО"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"ЛАМПА ДЕННОГО СВІТЛА"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"ХМАРНО"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Режим зйомки"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Автом."</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Спорт"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ніч"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Захід сонця"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"У приміщенні"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"НЕМАЄ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"СПОРТ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"НІЧ"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"ЗАХІД СОНЦЯ"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"ВЕЧІРКА"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ТАЙМЕР ЗВОРОТНОГО ВІДЛІКУ"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ТАЙМЕР ВИМКНЕНО"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 СЕКУНДА"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 СЕКУНДИ"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 СЕКУНД"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 СЕКУНД"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Неможливо вибрати в режимі зйомки."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Експозиція"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ЕКСПОЗИЦІЯ"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"КАМЕРА НА ПЕРЕДНІЙ ПАНЕЛІ"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"КАМЕРА НА ЗАДНІЙ ПАНЕЛІ"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"ОК"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"На носії USB недостатньо місця. Змініть налаштування якості чи видаліть зображення або інші файли."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"На карті SD недостатньо місця. Змініть налаштування якості чи видаліть зображення або інші файли."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Досягн. макс. розмір."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Зашвидко"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Підготовка панорами"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Не вдалося зберегти панораму."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Панорама"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Панорамна зйомка"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Очікування на попередню панораму"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Збереження..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Обробка панорами"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Торкніться, щоб фокусувати."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Ефекти"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Немає"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Стиснення"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Великі очі"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Великий рот"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Маленький рот"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Великий ніс"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Маленькі очі"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"У космосі"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Захід сонця"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Ваше відео"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Покладіть свій пристрій."\n"На декілька секунд вийдіть із поля зору."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Торкніться, щоб сфотографувати під час запису."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Запис відео розпочався."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Запис відео припинився."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Функцію миттєвого знімка відео вимкнено, коли ввімк. спецефекти."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Очистити ефекти"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"КУМЕДНІ ОБЛИЧЧЯ"</string>
+    <string name="effect_background" msgid="6579360207378171022">"ФОН"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Кнопка \"Витримка\""</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Кнопка меню"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Останні фото"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Перемикач між передньою та задньою камерами"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Перемикач фото, відео чи панорами"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Інші елементи керування налаштуваннями"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Закрити елементи керування налаштуваннями"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Керувати масштабом"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Зменшити %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Збільшити %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Прапорець %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Перейти в режим фото"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Перейти в режим відео"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Перейти в режим панорами"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Перейти в режим нової панорами"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Cкасувати перегляд"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Виконати перегляд"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Повторити в режимі перегляду"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Відтворити відео"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Призупинити відео"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Перезавантажити відео"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Панель часу відеопрогравача"</string>
+    <string name="capital_on" msgid="5491353494964003567">"УВІМКНЕНО"</string>
+    <string name="capital_off" msgid="7231052688467970897">"ВИМКНЕНО"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Вимкнено"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 секунда"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 секунд"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 секунди"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 хвилина"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 хвилин"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 хвилин"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 хвилин"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 хвилин"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 хвилин"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 хвилини"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 година"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 години"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 годин"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 годин"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 годин"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 годин"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 годин"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 години"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"с."</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"хв."</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"год."</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Готово"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Установити інтервал часу"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Функцію уповільненої зйомки вимкнено. Щоб установити інтервал часу, увімкніть її."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Таймер зворотного відліку вимкнено. Увімкніть його, щоб розпочати відлік перед зйомкою."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Установити тривалість у секундах"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Відлік перед зйомкою"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Зберігати місця з фотографій?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Додавайте до своїх фотографій і відео теги про місця, де їх було зроблено."\n\n"Інші програми можуть отримувати доступ до цієї інформації, а також ваших збережених зображень."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Ні, дякую"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Так"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Камера"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Пошук"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Фотографії"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Альбоми"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"ІНШІ ОПЦІЇ"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"НАЛАШТУВАННЯ"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d фото"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d фото"</item>
+  </plurals>
 </resources>
diff --git a/res/values-v11/styles.xml b/res/values-v11/styles.xml
new file mode 100644
index 0000000..d4105a3
--- /dev/null
+++ b/res/values-v11/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- When an activity requests a theme with an action bar from its manifest,
+         the activity preview window created by the system process while the
+         real activity is loading will also contain an action bar. Set this to
+         NoActionBar and change the theme in onCreate. -->
+    <style name="Theme.CameraBase" parent="android:Theme.Holo.NoActionBar.Fullscreen"/>
+    <style name="Widget.Button.Borderless" parent="android:Widget.Holo.Button.Borderless"/>
+</resources>
+
diff --git a/res/values-v13/styles.xml b/res/values-v13/styles.xml
new file mode 100644
index 0000000..10162b0
--- /dev/null
+++ b/res/values-v13/styles.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="TextAppearance.DialogWindowTitle" parent="@android:style/TextAppearance.Holo.DialogWindowTitle"/>
+    <style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Holo.Medium"/>
+</resources>
+
diff --git a/res/values-v14/styles.xml b/res/values-v14/styles.xml
index b6d6e43..c05bf30 100644
--- a/res/values-v14/styles.xml
+++ b/res/values-v14/styles.xml
@@ -19,4 +19,11 @@
         <item name="listPreferredItemHeightSmall">?android:attr/listPreferredItemHeightSmall</item>
         <item name="switchStyle">@android:style/Widget.CompoundButton</item>
     </style>
+    <style name="ActionBarTwoLineItem">
+        <item name="android:background">?android:attr/activatedBackgroundIndicator</item>
+    </style>
+    <style name="Theme.Photos.Gallery" parent="android:Theme.Holo.Light">
+    </style>
+    <style name="Theme.Photos.Fullscreen" parent="android:Theme.Holo">
+    </style>
 </resources>
diff --git a/res/values-vi/filtershow_strings.xml b/res/values-vi/filtershow_strings.xml
index c7ead9a..01f81f2 100644
--- a/res/values-vi/filtershow_strings.xml
+++ b/res/values-vi/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Không thể tải hình ảnh!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Đang đặt hình nền"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Không thể tải xuống ảnh. Không có mạng."</string>
     <string name="original" msgid="3524493791230430897">"Gốc"</string>
     <string name="borders" msgid="2067345080568684614">"Đường viền"</string>
-    <string name="done" msgid="3112344807927554662">"Xong"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Hoàn tác"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Làm lại"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Hiển thị lịch sử"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Ẩn lịch sử"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Hiện tr.thái hình ảnh"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Ẩn tr.thái hình ảnh"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Hiển thị các hiệu ứng được áp dụng"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Ẩn các hiệu ứng được áp dụng"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Cài đặt"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Có các thay đổi chưa được lưu đối với hình ảnh này."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Bạn có muốn lưu trước khi thoát không?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Lưu và thoát"</string>
+    <string name="exit" msgid="242642957038770113">"Thoát"</string>
     <string name="history" msgid="455767361472692409">"Lịch sử"</string>
     <string name="reset" msgid="9013181350779592937">"Đặt lại"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Trạng thái hình ảnh hiện tại"</string>
+    <string name="imageState" msgid="8632586742752891968">"Các hiệu ứng được áp dụng"</string>
     <string name="compare_original" msgid="8140838959007796977">"So sánh"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Áp dụng"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Đặt lại"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Không có"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Cố định"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Hành tinh nhỏ"</string>
     <string name="exposure" msgid="6526397045949374905">"Độ phơi sáng"</string>
     <string name="sharpness" msgid="6463103068318055412">"Độ sắc nét"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"Màu tự động"</string>
     <string name="hue" msgid="6231252147971086030">"Màu sắc"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Bóng"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Vùng sáng"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Đồ thị màu"</string>
     <string name="vignette" msgid="934721068851885390">"Làm mờ nét ảnh"</string>
     <string name="redeye" msgid="4508883127049472069">"Mắt đỏ"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Vẽ"</string>
     <string name="straighten" msgid="26025591664983528">"Làm thẳng"</string>
     <string name="crop" msgid="5781263790107850771">"Cắt"</string>
     <string name="rotate" msgid="2796802553793795371">"Xoay"</string>
     <string name="mirror" msgid="5482518108154883096">"Phản chiếu"</string>
+    <string name="negative" msgid="6998313764388022201">"Âm bản"</string>
     <string name="none" msgid="6633966646410296520">"Không có"</string>
+    <string name="edge" msgid="7036064886242147551">"Cạnh"</string>
+    <string name="kmeans" msgid="1630263230946107457">"Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"Giảm mẫu"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Đỏ"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Xanh lục"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Xanh lam"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Kiểu"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Kích thước"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Màu"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Đường vẽ"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Bút dạ"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"Bút vẽ"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Xóa"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Chọn màu tùy chỉnh"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Chọn màu"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Chọn kích thước"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"OK"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Gốc"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Kết quả"</string>
 </resources>
diff --git a/res/values-vi/photoeditor_strings.xml b/res/values-vi/photoeditor_strings.xml
deleted file mode 100644
index 791c959..0000000
--- a/res/values-vi/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Studio ảnh"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Không thể tải ảnh"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Không thể lưu ảnh đã chỉnh sửa"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Đã lưu ảnh chỉnh sửa vào <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Hủy các thay đổi chưa lưu?"</string>
-    <string name="yes" msgid="5402582493291792293">"Có"</string>
-    <string name="save" msgid="5516670392524294967">"LƯU"</string>
-    <string name="autofix" msgid="1663414996270538748">"T.động sửa"</string>
-    <string name="crop" msgid="7598378507763334041">"Cắt"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"Tạo màu sắc khác lạ"</string>
-    <string name="documentary" msgid="50396326708699797">"Phim tài liệu"</string>
-    <string name="doodle" msgid="1686409894518940990">"Hình vẽ nguệch ngoạc"</string>
-    <string name="duotone" msgid="8145893940788467106">"Có hai màu"</string>
-    <string name="facelift" msgid="6205748523156172637">"Làm sáng mặt"</string>
-    <string name="facetan" msgid="4412831806626044111">"Hiệu ứng làm rám nắng khuôn mặt"</string>
-    <string name="filllight" msgid="2644989991700022526">"Tô sáng"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Mắt cá"</string>
-    <string name="flip" msgid="2357692401826287480">"Lật"</string>
-    <string name="grain" msgid="7487585304579789098">"Hạt mỏng"</string>
-    <string name="grayscale" msgid="7641563843060945228">"Đen&amp;Trắng"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Đen trắng"</string>
-    <string name="highlight" msgid="3902653944386623972">"Thêm điểm sáng"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Tạo âm bản"</string>
-    <string name="posterize" msgid="4139212359561383385">"Ảnh áp phích"</string>
-    <string name="redeye" msgid="4958448806369928239">"Mắt đỏ"</string>
-    <string name="rotate" msgid="6607597269792373083">"Xoay"</string>
-    <string name="saturation" msgid="8621322012271169931">"Bão hòa"</string>
-    <string name="sepia" msgid="7978093531824705601">"Nâu đỏ"</string>
-    <string name="shadow" msgid="8235188588101973090">"Bóng"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Làm sắc nét"</string>
-    <string name="straighten" msgid="5217801513491493491">"Làm thẳng"</string>
-    <string name="temperature" msgid="1607987938521534517">"Độ ấm"</string>
-    <string name="tint" msgid="154435943863418434">"Phủ màu"</string>
-    <string name="vignette" msgid="7648125924662648282">"Làm mờ nét ảnh"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Kéo các điểm đánh dấu để cắt ảnh"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Kéo trên ảnh để vẽ ngoạch ngoạc"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Kéo ảnh để lật"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Chạm vào mắt đỏ để xóa hiệu ứng mắt đỏ"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Kéo ảnh để xoay"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Kéo ảnh để sắp thẳng"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Hiệu ứng phơi sáng"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Hiệu ứng màu"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Hiệu ứng nghệ thuật"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Sửa"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Hoàn tác"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Làm lại"</string>
-</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index c59e24c..aaea436 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Xoay phải"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Không thể tìm thấy mục."</string>
     <string name="edit" msgid="1502273844748580847">"Chỉnh sửa"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Chỉnh sửa đơn giản"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Đang xử lý yêu cầu lưu vào bộ nhớ cache"</string>
     <string name="caching_label" msgid="4521059045896269095">"Lưu cache..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Cắt"</string>
     <string name="trim_action" msgid="703098114452883524">"Cắt"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Ẩn"</string>
     <string name="set_as" msgid="3636764710790507868">"Đặt làm"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Không thể ẩn video."</string>
     <string name="video_err" msgid="7003051631792271009">"Không thể phát video."</string>
     <string name="group_by_location" msgid="316641628989023253">"Theo vị trí"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Theo thời gian"</string>
@@ -141,6 +144,7 @@
     <string name="auto" msgid="4296941368722892821">"Tự động"</string>
     <string name="flash_on" msgid="7891556231891837284">"Sử dụng flash"</string>
     <string name="flash_off" msgid="1445443413822680010">"Không có flash"</string>
+    <string name="unknown" msgid="3506693015896912952">"Không xác định"</string>
     <string name="ffx_original" msgid="372686331501281474">"Gốc"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"Cổ điển"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Instant"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Không có bộ nhớ ngoài nào"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Chế độ xem cuộn phim"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Chế độ xem lưới"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Xem toàn màn hình"</string>
     <string name="trimming" msgid="9122385768369143997">"Đang cắt ngắn"</string>
+    <string name="muting" msgid="5094925919589915324">"Đang ẩn"</string>
     <string name="please_wait" msgid="7296066089146487366">"Vui lòng chờ"</string>
-    <string name="save_into" msgid="4960537214388766062">"Đang lưu video đã được cắt vào album :"</string>
+    <string name="save_into" msgid="9155488424829609229">"Đang lưu video vào <xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Không thể cắt ngắn : video đích quá ngắn"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Hiển thị ảnh toàn cảnh"</string>
     <string name="save" msgid="613976532235060516">"Lưu"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Đang quét nội dung..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d mục được quét"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d mục được quét"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d mục được quét"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Đang sắp xếp..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Đã quét xong"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Đang nhập..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Không có nội dung nào để nhập vào thiết bị này."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Không có thiết bị MTP nào được kết nối"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Lỗi máy ảnh"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Không thể kết nối với máy ảnh."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Máy ảnh đã bị tắt do chính sách bảo mật."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Máy ảnh"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Máy quay video"</string>
+    <string name="wait" msgid="8600187532323801552">"Vui lòng đợi..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Kết nối bộ nhớ USB trước khi sử dụng máy ảnh."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Lắp thẻ SD trước khi sử dụng máy ảnh."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Đang chuẩn bị bộ nhớ USB…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Đang chuẩn bị thẻ SD…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Không thể truy cập bộ nhớ USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Không thể truy cập thẻ SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"HUỶ"</string>
+    <string name="review_ok" msgid="1156261588693116433">"XONG"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Đang ghi âm khoảng thời gian"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Chọn máy ảnh"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Quay lại"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Trước"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Vị trí lưu trữ"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"VỊ TRÍ"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Đồng hồ đếm ngược"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 giây"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d giây"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Kêu bíp khi đếm ngược"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Tắt"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Bật"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Chất lượng video"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Cao"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Thấp"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Chế độ thời gian trôi"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Cài đặt máy ảnh"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Cài đặt máy quay video"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Kích thước ảnh"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M pixel"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M pixel"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M pixel"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M pixel"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M pixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M pixel"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M pixel (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1,3M pixel"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M pixel"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Chế độ tiêu điểm"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Tự động"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Vô cùng"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Macro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"TỰ ĐỘNG"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"VÔ CỰC"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"MACRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Chế độ flash"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"CHẾ ĐỘ FLASH"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Tự động"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Bật"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Tắt"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"FLASH TỰ ĐỘNG"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"BẬT FLASH"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"TẮT FLASH"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Cân bằng trắng"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"CÂN BẰNG TRẮNG"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Tự động"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Ánh sáng nóng"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Ánh sáng ban ngày"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Huỳnh quang"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Nhiều mây"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"TỰ ĐỘNG"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"ÁNH SÁNG NÓNG"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"ÁNH SÁNG BAN NGÀY"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"HUỲNH QUANG"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"NHIỀU MÂY"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Chế độ chụp cảnh"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Tự động"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Tác vụ"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ban đêm"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Hoàng hôn"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Bữa tiệc"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"KHÔNG CÓ"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"TÁC VỤ"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"ĐÊM"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"HOÀNG HÔN"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"BỮA TIỆC"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ĐỒNG HỒ ĐẾM NGƯỢC"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"TẮT BỘ TÍNH GIỜ"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 GIÂY"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 GIÂY"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 GIÂY"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 GIÂY"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Không thể chọn trong chế độ cảnh."</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Phơi sáng"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"ĐỘ PHƠI SÁNG"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"MÁY ẢNH MẶT TRƯỚC"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"MÁY ẢNH MẶT SAU"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"OK"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Bộ nhớ USB của bạn sắp hết dung lượng. Thay đổi cài đặt chất lượng hoặc xóa một số ảnh hoặc tệp khác."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Thẻ SD của bạn sắp hết dung lượng. Thay đổi cài đặt chất lượng hoặc xóa một số ảnh hoặc tệp khác."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Đã đạt tới giới hạn kích thước."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Quá nhanh"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Đang tạo xem trước toàn cảnh"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Không thể lưu toàn cảnh."</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"Toàn cảnh"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Đang chụp toàn cảnh"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Đang đợi ảnh toàn cảnh trước đó"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Đang lưu…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Đang hiển thị ảnh toàn cảnh"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Chạm để lấy tiêu cự."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Hiệu ứng"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Bỏ chọn tất cả"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Xoa"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Đôi mắt to"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Miệng lớn"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Miệng nhỏ"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Mũi to"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Đôi mắt nhỏ"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Trong không gian"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Hoàng hôn"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Video của bạn"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Dừng thiết bị của bạn."\n"Thoát khỏi chế độ xem trong giây lát."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Chạm để chụp ảnh trong khi quay."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Đã bắt đầu quay video."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Đã dừng quay video."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Chụp nhanh video bị vô hiệu khi các hiệu ứng đặc biệt được bật."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Xóa hiệu ứng"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"MẶT XẤU"</string>
+    <string name="effect_background" msgid="6579360207378171022">"MÀU NỀN"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Nút chụp"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Nút trình đơn"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Ảnh gần đây nhất"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Chuyển đổi giữa máy ảnh trước và sau"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Bộ chọn chế độ máy ảnh, video hoặc toàn cảnh"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Kiểm soát cài đặt khác"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Đóng kiểm soát cài đặt"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Điều khiển thu phóng"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Giảm %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Tăng %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"Hộp kiểm %1$s"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Chuyển sang ảnh"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Chuyển sang video"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Chuyển sang chế độ toàn cảnh"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Chuyển sang chế độ toàn cảnh mới"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Hủy bài đánh giá"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Đánh giá xong"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Xem lại ảnh chụp lại"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Phát video"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Tạm dừng video"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Tải lại video"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Thanh thời gian của trình phát video"</string>
+    <string name="capital_on" msgid="5491353494964003567">"BẬT"</string>
+    <string name="capital_off" msgid="7231052688467970897">"TẮT"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Tắt"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0,5 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1,5 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2,5 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 giây"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0,5 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1,5 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2,5 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 phút"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0,5 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1,5 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2,5 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 giờ"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 giờ"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"giây"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"phút"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"giờ"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Xong"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Đặt khoảng thời gian"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Tính năng thời gian trôi bị tắt. Bật tính năng này để đặt khoảng thời gian."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Đồng hồ đếm ngược bị tắt. Bật đồng hồ để đếm ngược trước khi chụp ảnh."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Đặt thời gian trong vài giây"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Đếm ngược để chụp ảnh"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Nhớ vị trí chụp ảnh?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Gắn thẻ cho ảnh và video của bạn với những địa điểm mà ảnh đó được chụp và video đó được quay."\n\n"Các ứng dụng khác có thể truy cập vào thông tin này cùng với các hình ảnh đã lưu của bạn."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Không, cảm ơn"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Có"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Máy ảnh"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Tìm kiếm"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Ảnh"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Album"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"TÙY CHỌN KHÁC"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"CÀI ĐẶT"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d ảnh"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d ảnh"</item>
+  </plurals>
 </resources>
diff --git a/res/values-xlarge-land/drawable.xml b/res/values-xlarge-land/drawable.xml
new file mode 100644
index 0000000..2994479
--- /dev/null
+++ b/res/values-xlarge-land/drawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<!-- Caution: Don't merge with res/values-xlarge-port/drawable.xml, otherwise
+     the resources listed in this file wouldn't be reloaded when device
+     orientation changes. -->
+<resources>
+    <item name="btn_video_shutter_recording_holo" type="drawable">@drawable/btn_video_shutter_recording_holo_xlarge</item>
+    <item name="btn_video_shutter_recording_pressed_holo" type="drawable">@drawable/btn_video_shutter_recording_pressed_holo_xlarge</item>
+</resources>
diff --git a/res/values-xlarge-port/drawable.xml b/res/values-xlarge-port/drawable.xml
new file mode 100644
index 0000000..caacf47
--- /dev/null
+++ b/res/values-xlarge-port/drawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<!-- Caution: Don't merge with res/values-xlarge-land/drawable.xml, otherwise
+     the resources listed in this file wouldn't be reloaded when device
+     orientation changes. -->
+<resources>
+    <item name="btn_video_shutter_recording_holo" type="drawable">@drawable/btn_video_shutter_recording_holo_xlarge</item>
+    <item name="btn_video_shutter_recording_pressed_holo" type="drawable">@drawable/btn_video_shutter_recording_pressed_holo_xlarge</item>
+</resources>
diff --git a/res/values-xlarge/dimens.xml b/res/values-xlarge/dimens.xml
new file mode 100644
index 0000000..51b3dad
--- /dev/null
+++ b/res/values-xlarge/dimens.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <dimen name="pano_mosaic_surface_height">@dimen/pano_mosaic_surface_height_xlarge</dimen>
+    <dimen name="pano_review_button_width">@dimen/pano_review_button_width_xlarge</dimen>
+    <dimen name="pano_review_button_height">@dimen/pano_review_button_height_xlarge</dimen>
+    <dimen name="setting_row_height">@dimen/setting_row_height_xlarge</dimen>
+    <dimen name="setting_item_text_size">@dimen/setting_item_text_size_xlarge</dimen>
+    <dimen name="setting_knob_width">@dimen/setting_knob_width_xlarge</dimen>
+    <dimen name="setting_item_text_width">@dimen/setting_item_text_width_xlarge</dimen>
+    <dimen name="setting_popup_window_width">@dimen/setting_popup_window_width_xlarge</dimen>
+    <dimen name="setting_item_list_margin">@dimen/setting_item_list_margin_xlarge</dimen>
+    <dimen name="indicator_bar_width">@dimen/indicator_bar_width_xlarge</dimen>
+    <dimen name="popup_title_text_size">@dimen/popup_title_text_size_xlarge</dimen>
+    <dimen name="popup_title_frame_min_height">@dimen/popup_title_frame_min_height_xlarge</dimen>
+    <dimen name="big_setting_popup_window_width">@dimen/big_setting_popup_window_width_xlarge</dimen>
+    <dimen name="setting_item_icon_width">@dimen/setting_item_icon_width_xlarge</dimen>
+    <dimen name="effect_setting_item_icon_width">@dimen/effect_setting_item_icon_width_xlarge</dimen>
+    <dimen name="effect_setting_item_text_size">@dimen/effect_setting_item_text_size_xlarge</dimen>
+    <dimen name="effect_setting_type_text_size">@dimen/effect_setting_type_text_size_xlarge</dimen>
+    <dimen name="effect_setting_type_text_min_height">@dimen/effect_setting_type_text_min_height_xlarge</dimen>
+    <dimen name="effect_setting_clear_text_size">@dimen/effect_setting_clear_text_size_xlarge</dimen>
+    <dimen name="effect_setting_clear_text_min_height">@dimen/effect_setting_clear_text_min_height_xlarge</dimen>
+    <dimen name="effect_setting_type_text_left_padding">@dimen/effect_setting_type_text_left_padding_xlarge</dimen>
+    <dimen name="onscreen_indicators_height">@dimen/onscreen_indicators_height_xlarge</dimen>
+    <dimen name="onscreen_exposure_indicator_text_size">@dimen/onscreen_exposure_indicator_text_size_xlarge</dimen>
+</resources>
+
diff --git a/res/values-xlarge/drawable.xml b/res/values-xlarge/drawable.xml
new file mode 100644
index 0000000..648f1d7
--- /dev/null
+++ b/res/values-xlarge/drawable.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012, 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.
+-->
+<resources>
+    <item name="ic_effects_holo_light" type="drawable">@drawable/ic_effects_holo_light_xlarge</item>
+    <item name="ic_pan_border_fast" type="drawable">@drawable/ic_pan_border_fast_xlarge</item>
+    <item name="ic_pan_left_indicator_fast" type="drawable">@drawable/ic_pan_left_indicator_fast_xlarge</item>
+    <item name="ic_pan_left_indicator" type="drawable">@drawable/ic_pan_left_indicator_xlarge</item>
+    <item name="ic_pan_progression" type="drawable">@drawable/ic_pan_progression_xlarge</item>
+    <item name="ic_pan_right_indicator_fast" type="drawable">@drawable/ic_pan_right_indicator_fast_xlarge</item>
+    <item name="ic_pan_right_indicator" type="drawable">@drawable/ic_pan_right_indicator_xlarge</item>
+    <item name="ic_scn_holo_light" type="drawable">@drawable/ic_scn_holo_light_xlarge</item>
+    <item name="ic_snapshot_border" type="drawable">@drawable/ic_snapshot_border_xlarge</item>
+    <item name="ic_switch_photo_facing_holo_light" type="drawable">@drawable/ic_switch_photo_facing_holo_light_xlarge</item>
+    <item name="ic_switch_video_facing_holo_light" type="drawable">@drawable/ic_switch_video_facing_holo_light_xlarge</item>
+    <item name="ic_timelapse_none" type="drawable">@drawable/ic_timelapse_none_xlarge</item>
+</resources>
diff --git a/res/values-xlarge/filtershow_values.xml b/res/values-xlarge/filtershow_values.xml
new file mode 100644
index 0000000..1098ee0
--- /dev/null
+++ b/res/values-xlarge/filtershow_values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- Specify the screen orientation -->
+    <bool name="only_use_portrait">false</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values-xlarge/styles.xml b/res/values-xlarge/styles.xml
index 2c6385a..55b29b1 100644
--- a/res/values-xlarge/styles.xml
+++ b/res/values-xlarge/styles.xml
@@ -17,4 +17,13 @@
     <style name="DialogPickerTheme" parent="android:Theme.Holo.Dialog">
     </style>
     <bool name="picker_is_dialog">true</bool>
+
+    <!-- Camera resources below -->
+
+    <style name="ReviewControlText" parent="@style/ReviewControlText_xlarge" />
+    <style name="PopupTitleText" parent="@style/PopupTitleText_xlarge" />
+    <style name="PanoCustomDialogText" parent="@style/PanoCustomDialogText_xlarge" />
+    <style name="ViewfinderLabelLayout" parent="@style/ViewfinderLabelLayout_xlarge" />
+    <style name="SettingPopupWindow" parent="@style/SettingPopupWindow_xlarge" />
+
 </resources>
diff --git a/res/values-zh-rCN/filtershow_strings.xml b/res/values-zh-rCN/filtershow_strings.xml
index daf004c..1c17e79 100644
--- a/res/values-zh-rCN/filtershow_strings.xml
+++ b/res/values-zh-rCN/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"无法加载该图片!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"正在设置壁纸"</string>
+    <string name="download_failure" msgid="5923323939788582895">"未连接到网络,因此无法下载照片。"</string>
     <string name="original" msgid="3524493791230430897">"原图"</string>
     <string name="borders" msgid="2067345080568684614">"边框"</string>
-    <string name="done" msgid="3112344807927554662">"完成"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"撤消"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"重做"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"显示历史记录"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"隐藏历史记录"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"显示图片状态"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"隐藏图片状态"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"显示应用的效果"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"隐藏应用的效果"</string>
     <string name="menu_settings" msgid="6428291655769260831">"设置"</string>
+    <string name="unsaved" msgid="8704442449002374375">"此图片的更改尚未保存。"</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"要在退出之前保存更改吗?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"保存并退出"</string>
+    <string name="exit" msgid="242642957038770113">"退出"</string>
     <string name="history" msgid="455767361472692409">"历史记录"</string>
     <string name="reset" msgid="9013181350779592937">"重置"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"当前的图片状态"</string>
+    <string name="imageState" msgid="8632586742752891968">"运用的效果"</string>
     <string name="compare_original" msgid="8140838959007796977">"比较"</string>
     <string name="apply_effect" msgid="1218288221200568947">"应用"</string>
     <string name="reset_effect" msgid="7712605581024929564">"重置"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"无"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"固定"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"小星球"</string>
     <string name="exposure" msgid="6526397045949374905">"曝光"</string>
     <string name="sharpness" msgid="6463103068318055412">"锐度"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"自动调整色彩"</string>
     <string name="hue" msgid="6231252147971086030">"色调"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"阴影"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"强光"</string>
     <string name="curvesRGB" msgid="915010781090477550">"曲线"</string>
     <string name="vignette" msgid="934721068851885390">"晕影"</string>
     <string name="redeye" msgid="4508883127049472069">"红眼"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"绘图"</string>
     <string name="straighten" msgid="26025591664983528">"拉直"</string>
     <string name="crop" msgid="5781263790107850771">"裁剪"</string>
     <string name="rotate" msgid="2796802553793795371">"旋转"</string>
     <string name="mirror" msgid="5482518108154883096">"镜像"</string>
+    <string name="negative" msgid="6998313764388022201">"负片"</string>
     <string name="none" msgid="6633966646410296520">"无"</string>
+    <string name="edge" msgid="7036064886242147551">"边缘亮化"</string>
+    <string name="kmeans" msgid="1630263230946107457">"波普效果"</string>
+    <string name="downsample" msgid="3552938534146980104">"下采样"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"红色"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"绿色"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"蓝色"</string>
+    <string name="draw_style" msgid="2036125061987325389">"样式"</string>
+    <string name="draw_size" msgid="4360005386104151209">"大小"</string>
+    <string name="draw_color" msgid="2119030386987211193">"颜色"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"线条"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"记号笔"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"喷笔"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"清除"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"选择自定义颜色"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"选择颜色"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"选择大小"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"确定"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"原图"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"结果"</string>
 </resources>
diff --git a/res/values-zh-rCN/photoeditor_strings.xml b/res/values-zh-rCN/photoeditor_strings.xml
deleted file mode 100644
index 36be6e7..0000000
--- a/res/values-zh-rCN/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"照相馆"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"无法加载该照片"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"无法保存经过编辑的照片"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"经过编辑的照片已保存至“<xliff:g id="FOLDER_NAME">%s</xliff:g>”"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"要舍弃未保存的更改吗?"</string>
-    <string name="yes" msgid="5402582493291792293">"是"</string>
-    <string name="save" msgid="5516670392524294967">"保存"</string>
-    <string name="autofix" msgid="1663414996270538748">"自动修正"</string>
-    <string name="crop" msgid="7598378507763334041">"修剪"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"交叉冲印"</string>
-    <string name="documentary" msgid="50396326708699797">"纪实"</string>
-    <string name="doodle" msgid="1686409894518940990">"涂鸦"</string>
-    <string name="duotone" msgid="8145893940788467106">"双色"</string>
-    <string name="facelift" msgid="6205748523156172637">"面部发光效果"</string>
-    <string name="facetan" msgid="4412831806626044111">"面部古铜色效果"</string>
-    <string name="filllight" msgid="2644989991700022526">"补光"</string>
-    <string name="fisheye" msgid="6037488646928998921">"鱼眼镜头"</string>
-    <string name="flip" msgid="2357692401826287480">"翻转"</string>
-    <string name="grain" msgid="7487585304579789098">"胶片颗粒"</string>
-    <string name="grayscale" msgid="7641563843060945228">"黑白"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"黑白"</string>
-    <string name="highlight" msgid="3902653944386623972">"加亮区"</string>
-    <string name="lomoish" msgid="1270032154357186736">"LOMO 风格"</string>
-    <string name="negative" msgid="1985508917342811252">"反色"</string>
-    <string name="posterize" msgid="4139212359561383385">"色调分离"</string>
-    <string name="redeye" msgid="4958448806369928239">"红眼"</string>
-    <string name="rotate" msgid="6607597269792373083">"旋转"</string>
-    <string name="saturation" msgid="8621322012271169931">"饱和度"</string>
-    <string name="sepia" msgid="7978093531824705601">"深褐色"</string>
-    <string name="shadow" msgid="8235188588101973090">"阴影"</string>
-    <string name="sharpen" msgid="8449662378104403230">"锐化"</string>
-    <string name="straighten" msgid="5217801513491493491">"拉直"</string>
-    <string name="temperature" msgid="1607987938521534517">"暖色调"</string>
-    <string name="tint" msgid="154435943863418434">"着色"</string>
-    <string name="vignette" msgid="7648125924662648282">"晕影"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"拖动标记以裁剪照片"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"在照片上绘制即可涂鸦"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"拖动照片可将其翻转"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"点按即可消除红眼"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"拖动照片可将其旋转"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"拖动照片可将其拉直"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"曝光效果"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"颜色效果"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"艺术效果"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"修正"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"撤消"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"重做"</string>
-</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7c4ef63..ffe2cd3 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -21,13 +21,13 @@
     <string name="details_ms" msgid="940634969189855292">"%1$02d:%2$02d"</string>
     <string name="details_hms" msgid="3215779248094151255">"%1$d:%2$02d:%3$02d"</string>
     <string name="movie_view_label" msgid="3526526872644898229">"视频播放器"</string>
-    <string name="loading_video" msgid="4013492720121891585">"正在载入视频..."</string>
-    <string name="loading_image" msgid="1200894415793838191">"正在载入图片..."</string>
+    <string name="loading_video" msgid="4013492720121891585">"正在加载视频..."</string>
+    <string name="loading_image" msgid="1200894415793838191">"正在加载图片..."</string>
     <string name="loading_account" msgid="928195413034552034">"正在加载帐户…"</string>
     <string name="resume_playing_title" msgid="8996677350649355013">"继续播放视频"</string>
     <string name="resume_playing_message" msgid="5184414518126703481">"从 %s 开始继续播放?"</string>
     <string name="resume_playing_resume" msgid="3847915469173852416">"继续播放"</string>
-    <string name="loading" msgid="7038208555304563571">"正在载入..."</string>
+    <string name="loading" msgid="7038208555304563571">"正在加载..."</string>
     <string name="fail_to_load" msgid="8394392853646664505">"无法加载"</string>
     <string name="fail_to_load_image" msgid="6155388718549782425">"无法加载此图片"</string>
     <string name="no_thumbnail" msgid="284723185546429750">"无缩略图"</string>
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"向右旋转"</string>
     <string name="no_such_item" msgid="5315144556325243400">"找不到指定的项。"</string>
     <string name="edit" msgid="1502273844748580847">"编辑"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"简单编辑"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"正在处理缓存请求"</string>
     <string name="caching_label" msgid="4521059045896269095">"正在缓存..."</string>
     <string name="crop_action" msgid="3427470284074377001">"修剪"</string>
     <string name="trim_action" msgid="703098114452883524">"修剪"</string>
+    <string name="mute_action" msgid="5296241754753306251">"静音"</string>
     <string name="set_as" msgid="3636764710790507868">"设置为"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"无法将视频静音。"</string>
     <string name="video_err" msgid="7003051631792271009">"无法播放视频。"</string>
     <string name="group_by_location" msgid="316641628989023253">"按位置分组"</string>
     <string name="group_by_time" msgid="9046168567717963573">"按时间分组"</string>
@@ -141,9 +144,10 @@
     <string name="auto" msgid="4296941368722892821">"自动"</string>
     <string name="flash_on" msgid="7891556231891837284">"使用了闪光灯"</string>
     <string name="flash_off" msgid="1445443413822680010">"未使用闪光灯"</string>
+    <string name="unknown" msgid="3506693015896912952">"未知"</string>
     <string name="ffx_original" msgid="372686331501281474">"原照片"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"复古"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"瞬时"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"即时出相"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"漂除银影"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"蓝色"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"黑白"</string>
@@ -193,10 +197,245 @@
     <string name="no_external_storage" msgid="95726173164068417">"没有可用的外部存储设备"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"幻灯片视图"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"网格视图"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"全屏视图"</string>
     <string name="trimming" msgid="9122385768369143997">"正在剪辑"</string>
+    <string name="muting" msgid="5094925919589915324">"正在静音"</string>
     <string name="please_wait" msgid="7296066089146487366">"请稍候"</string>
-    <string name="save_into" msgid="4960537214388766062">"正在将剪辑视频保存到相册:"</string>
+    <string name="save_into" msgid="9155488424829609229">"正在将视频保存到“<xliff:g id="ALBUM_NAME">%1$s</xliff:g>”…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"无法剪辑:目标视频太短"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"正在渲染全景图"</string>
     <string name="save" msgid="613976532235060516">"保存"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"正在扫描内容..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"已扫描%1$d项"</item>
+    <item quantity="one" msgid="4340019444460561648">"已扫描%1$d项"</item>
+    <item quantity="other" msgid="3138021473860555499">"已扫描%1$d项"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"正在排序..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"扫描已完成"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"正在导入..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"这台设备上没有可导入的内容。"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"未连接 MTP 设备"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"相机故障"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"无法连接到相机。"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"由于安全政策的限制,相机已被停用。"</string>
+    <string name="camera_label" msgid="6346560772074764302">"相机"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"摄像机"</string>
+    <string name="wait" msgid="8600187532323801552">"请稍候..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"使用相机前请先装载 USB 存储设备。"</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"使用相机前请先插入 SD 卡。"</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"正在准备 USB 存储设备..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"正在准备 SD 卡..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"无法访问 USB 存储设备。"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"无法访问 SD 卡。"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"取消"</string>
+    <string name="review_ok" msgid="1156261588693116433">"完成"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"延时录制"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"选择摄像头"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"背面相机"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"正面相机"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"保存所在位置"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"位置"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"倒计时器"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1秒"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d秒"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"倒计时过程中发出提示音"</string>
+    <string name="setting_off" msgid="4480039384202951946">"关闭"</string>
+    <string name="setting_on" msgid="8602246224465348901">"打开"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"视频画质"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"高画质"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"低画质"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"延时"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"相机设置"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"摄像机设置"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"照片大小"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"1300万像素"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"800 万像素"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"500 万像素"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_4mp (933242108272469142) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"300 万像素"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"200 万像素"</string>
+    <!-- no translation found for pref_camera_picturesize_entry_2mp_wide (5359490802026616612) -->
+    <skip />
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"130 万像素"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"100 万像素"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"对焦方式"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"自动"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"无限远"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"微距"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"自动"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"无限远"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"微距"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"闪光模式"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"闪光灯模式"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"自动"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"开"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"关"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"自动闪光灯"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"打开闪光灯"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"关闭闪光灯"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"白平衡"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"白平衡"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"自动"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"白炽光"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"日光"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"荧光"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"阴天"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"自动"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"白炽光"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"日光"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"荧光"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"阴天"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"取景模式"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"自动"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"运动"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"夜景"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"日落"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"派对"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"无"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"动作"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"夜晚"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"日落"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"聚会"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"倒计时器"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"关闭倒计时器"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1秒"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3秒"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10秒"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15秒"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"无法在取景模式下选择。"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"曝光"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"曝光"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"前置摄像头"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"后置摄像头"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"确定"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"USB 存储设备空间不足,请更改照片品质设置,或删除某些照片或者其他文件。"</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"SD 卡空间不足,请更改照片品质设置,或删除某些照片或者其他文件。"</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"已达到大小上限。"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"过快"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"正在生成全景图"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"无法保存全景照片。"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"全景图"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"正在拍摄全景照片"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"正在等待上一幅全景照片处理完毕"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"正在保存..."</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"正在渲染全景图"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"触摸对焦。"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"效果"</string>
+    <string name="effect_none" msgid="3601545724573307541">"无"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"面部哈哈镜"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"大眼睛"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"大嘴巴"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"小嘴巴"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"大鼻子"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"小眼睛"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"太空背景"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"日落"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"您的视频"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"把设备放好。"\n"离开镜头前片刻。"</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"在录制视频过程中,轻触一下即可拍一张照片。"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"已开始录制视频。"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"已停止录制视频。"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"启用特殊效果时停用视频快照。"</string>
+    <string name="clear_effects" msgid="5485339175014139481">"清除效果"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"趣味表情"</string>
+    <string name="effect_background" msgid="6579360207378171022">"背景"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"“快门”按钮"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"菜单按钮"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"最新照片"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"前视和后视相机开关"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"相机、视频或全景模式选择器"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"更多设置控件"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"关闭设置控件"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"缩放控件"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"减少%1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"增加%1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"“%1$s”复选框"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"切换到拍照模式"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"切换到视频模式"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"切换到全景模式"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"切换到新的全景模式"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"取消"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"完成"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"重拍"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"播放视频"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"暂停视频"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"重新加载视频"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"视频播放器时间栏"</string>
+    <string name="capital_on" msgid="5491353494964003567">"打开"</string>
+    <string name="capital_off" msgid="7231052688467970897">"关闭"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"关闭"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 分钟"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 小时"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 小时"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"秒"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"分钟"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"小时"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"完成"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"设置时间间隔"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"延时拍摄功能已关闭,要设置时间间隔,请先开启该功能。"</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"倒计时器功能目前已关闭。要在拍照前倒计时,请先开启该功能。"</string>
+    <string name="set_duration" msgid="5578035312407161304">"设置倒数时间(秒)"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"拍照倒计时中"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"是否记住照片拍摄地点?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"为您的照片和视频标明拍摄地点。"\n\n"其他应用在查看您保存的图片时将可以访问这些信息。"</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"不用了"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"是"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"相机"</string>
+    <string name="menu_search" msgid="7580008232297437190">"搜索"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"照片"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"相册"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"更多选项"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"设置"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d张照片"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d张照片"</item>
+  </plurals>
 </resources>
diff --git a/res/values-zh-rTW/filtershow_strings.xml b/res/values-zh-rTW/filtershow_strings.xml
index 9c98b53..776b749 100644
--- a/res/values-zh-rTW/filtershow_strings.xml
+++ b/res/values-zh-rTW/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"無法載入圖片!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"正在設定桌布"</string>
+    <string name="download_failure" msgid="5923323939788582895">"目前沒有網路連線,因此無法下載相片。"</string>
     <string name="original" msgid="3524493791230430897">"原始"</string>
     <string name="borders" msgid="2067345080568684614">"邊框"</string>
-    <string name="done" msgid="3112344807927554662">"完成"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"復原"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"重做"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"顯示紀錄"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"隱藏紀錄"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"顯示圖片狀態"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"隱藏圖片狀態"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"顯示套用的效果"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"隱藏套用的效果"</string>
     <string name="menu_settings" msgid="6428291655769260831">"設定"</string>
+    <string name="unsaved" msgid="8704442449002374375">"這張圖片的變更尚未儲存。"</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"您要在結束前儲存變更嗎?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"儲存並結束"</string>
+    <string name="exit" msgid="242642957038770113">"結束"</string>
     <string name="history" msgid="455767361472692409">"紀錄"</string>
     <string name="reset" msgid="9013181350779592937">"重設"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"目前圖片狀態"</string>
+    <string name="imageState" msgid="8632586742752891968">"套用的效果"</string>
     <string name="compare_original" msgid="8140838959007796977">"比較"</string>
     <string name="apply_effect" msgid="1218288221200568947">"套用"</string>
     <string name="reset_effect" msgid="7712605581024929564">"重設"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"無"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"固定"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"小星球"</string>
     <string name="exposure" msgid="6526397045949374905">"曝光"</string>
     <string name="sharpness" msgid="6463103068318055412">"銳利度"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"自動色彩校正"</string>
     <string name="hue" msgid="6231252147971086030">"色調"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"陰影"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"強光"</string>
     <string name="curvesRGB" msgid="915010781090477550">"曲線"</string>
     <string name="vignette" msgid="934721068851885390">"暈影"</string>
     <string name="redeye" msgid="4508883127049472069">"紅眼"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"繪圖"</string>
     <string name="straighten" msgid="26025591664983528">"拉正"</string>
     <string name="crop" msgid="5781263790107850771">"裁剪"</string>
     <string name="rotate" msgid="2796802553793795371">"旋轉"</string>
     <string name="mirror" msgid="5482518108154883096">"鏡像"</string>
+    <string name="negative" msgid="6998313764388022201">"負片效果"</string>
     <string name="none" msgid="6633966646410296520">"無"</string>
+    <string name="edge" msgid="7036064886242147551">"邊緣特效"</string>
+    <string name="kmeans" msgid="1630263230946107457">"安迪沃荷"</string>
+    <string name="downsample" msgid="3552938534146980104">"縮小取樣"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"紅色"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"綠色"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"藍色"</string>
+    <string name="draw_style" msgid="2036125061987325389">"樣式"</string>
+    <string name="draw_size" msgid="4360005386104151209">"尺寸"</string>
+    <string name="draw_color" msgid="2119030386987211193">"顏色"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"線條"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"彩色筆"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"潑灑"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"清除"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"選擇自訂顏色"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"選擇顏色"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"選擇尺寸"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"確定"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"原始"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"結果"</string>
 </resources>
diff --git a/res/values-zh-rTW/photoeditor_strings.xml b/res/values-zh-rTW/photoeditor_strings.xml
deleted file mode 100644
index de59988..0000000
--- a/res/values-zh-rTW/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"相片工作室"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"無法載入相片"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"無法儲存編輯完成的相片"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"編輯完成的相片已儲存至「<xliff:g id="FOLDER_NAME">%s</xliff:g>」"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"要捨棄尚未儲存的變更?"</string>
-    <string name="yes" msgid="5402582493291792293">"是"</string>
-    <string name="save" msgid="5516670392524294967">"儲存"</string>
-    <string name="autofix" msgid="1663414996270538748">"自動修正"</string>
-    <string name="crop" msgid="7598378507763334041">"裁剪"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"融合處理"</string>
-    <string name="documentary" msgid="50396326708699797">"黑白記錄片"</string>
-    <string name="doodle" msgid="1686409894518940990">"塗鴉"</string>
-    <string name="duotone" msgid="8145893940788467106">"雙色調"</string>
-    <string name="facelift" msgid="6205748523156172637">"光滑臉孔"</string>
-    <string name="facetan" msgid="4412831806626044111">"古銅色臉孔"</string>
-    <string name="filllight" msgid="2644989991700022526">"調整亮度"</string>
-    <string name="fisheye" msgid="6037488646928998921">"魚眼"</string>
-    <string name="flip" msgid="2357692401826287480">"翻轉"</string>
-    <string name="grain" msgid="7487585304579789098">"膠片顆粒"</string>
-    <string name="grayscale" msgid="7641563843060945228">"黑白"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"黑白"</string>
-    <string name="highlight" msgid="3902653944386623972">"強光"</string>
-    <string name="lomoish" msgid="1270032154357186736">"Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"負片"</string>
-    <string name="posterize" msgid="4139212359561383385">"色調分離"</string>
-    <string name="redeye" msgid="4958448806369928239">"紅眼修正"</string>
-    <string name="rotate" msgid="6607597269792373083">"旋轉"</string>
-    <string name="saturation" msgid="8621322012271169931">"飽和度"</string>
-    <string name="sepia" msgid="7978093531824705601">"懷舊色調"</string>
-    <string name="shadow" msgid="8235188588101973090">"陰影"</string>
-    <string name="sharpen" msgid="8449662378104403230">"銳化"</string>
-    <string name="straighten" msgid="5217801513491493491">"拉正"</string>
-    <string name="temperature" msgid="1607987938521534517">"暖色"</string>
-    <string name="tint" msgid="154435943863418434">"著色"</string>
-    <string name="vignette" msgid="7648125924662648282">"暈影"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"拖曳標記即可裁剪相片"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"在相片上畫圖即可塗鴉"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"拖曳照片即可翻轉"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"輕按紅眼即可予以移除"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"拖曳照片即可旋轉"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"拖曳照片即可拉正"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"曝光效果"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"色彩效果"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"藝術效果"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"修正"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"復原"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"重做"</string>
-</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 61ee5c6..c2b8d83 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -88,11 +88,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"向右旋轉"</string>
     <string name="no_such_item" msgid="5315144556325243400">"找不到項目。"</string>
     <string name="edit" msgid="1502273844748580847">"編輯"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"簡易編輯"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"正在處理快取要求"</string>
     <string name="caching_label" msgid="4521059045896269095">"快取中…"</string>
     <string name="crop_action" msgid="3427470284074377001">"裁剪"</string>
     <string name="trim_action" msgid="703098114452883524">"修剪"</string>
+    <string name="mute_action" msgid="5296241754753306251">"靜音"</string>
     <string name="set_as" msgid="3636764710790507868">"設為"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"無法將影片設為靜音。"</string>
     <string name="video_err" msgid="7003051631792271009">"無法播放影片。"</string>
     <string name="group_by_location" msgid="316641628989023253">"依位置"</string>
     <string name="group_by_time" msgid="9046168567717963573">"依時間"</string>
@@ -141,13 +144,14 @@
     <string name="auto" msgid="4296941368722892821">"自動"</string>
     <string name="flash_on" msgid="7891556231891837284">"使用閃光燈"</string>
     <string name="flash_off" msgid="1445443413822680010">"未使用閃光燈"</string>
+    <string name="unknown" msgid="3506693015896912952">"不明"</string>
     <string name="ffx_original" msgid="372686331501281474">"原始"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"復古"</string>
-    <string name="ffx_instant" msgid="726968618715691987">"即時"</string>
+    <string name="ffx_instant" msgid="726968618715691987">"拍立得"</string>
     <string name="ffx_bleach" msgid="8946700451603478453">"漂白"</string>
     <string name="ffx_blue_crush" msgid="6034283412305561226">"藍色"</string>
     <string name="ffx_bw_contrast" msgid="517988490066217206">"黑白"</string>
-    <string name="ffx_punch" msgid="1343475517872562639">"打孔"</string>
+    <string name="ffx_punch" msgid="1343475517872562639">"凹魚眼"</string>
     <string name="ffx_x_process" msgid="4779398678661811765">"X 光處理"</string>
     <string name="ffx_washout" msgid="4594160692176642735">"褐色"</string>
     <string name="ffx_washout_color" msgid="8034075742195795219">"石版"</string>
@@ -193,10 +197,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"沒有可用的外部儲存裝置"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"幻燈片檢視"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"格狀檢視"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"全螢幕檢視"</string>
     <string name="trimming" msgid="9122385768369143997">"修剪中"</string>
+    <string name="muting" msgid="5094925919589915324">"正在設為靜音"</string>
     <string name="please_wait" msgid="7296066089146487366">"請稍候"</string>
-    <string name="save_into" msgid="4960537214388766062">"正在將修剪後的影片儲存至相簿:"</string>
+    <string name="save_into" msgid="9155488424829609229">"正在將影片儲存至「<xliff:g id="ALBUM_NAME">%1$s</xliff:g>」…"</string>
     <string name="trim_too_short" msgid="751593965620665326">"無法修剪:目標影片長度過短"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"正在進行全景成像作業"</string>
     <string name="save" msgid="613976532235060516">"儲存"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"正在掃描內容..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"已掃描 %1$d 個項目"</item>
+    <item quantity="one" msgid="4340019444460561648">"已掃描 %1$d 個項目"</item>
+    <item quantity="other" msgid="3138021473860555499">"已掃描 %1$d 個項目"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"排序中..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"掃描完成"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"匯入中..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"沒有任何內容可供這個裝置匯入。"</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"未連接任何 MTP 裝置"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"相機發生錯誤"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"無法連接相機。"</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"由於安全性政策規定,相機已遭停用。"</string>
+    <string name="camera_label" msgid="6346560772074764302">"相機"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"攝錄影機"</string>
+    <string name="wait" msgid="8600187532323801552">"請稍候…"</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"使用相機前,請先插入 USB 儲存裝置。"</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"使用相機前,請先插入 SD 卡。"</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"正在準備 USB 儲存裝置…"</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"正在準備 SD 卡…"</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"無法存取 USB 儲存裝置。"</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"無法存取 SD 卡。"</string>
+    <string name="review_cancel" msgid="8188009385853399254">"取消"</string>
+    <string name="review_ok" msgid="1156261588693116433">"完成"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"延時攝影錄製"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"選擇相機"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"後置鏡頭"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"前置鏡頭"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"儲存位置"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"地點"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"倒數計時器"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 秒"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d 秒"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"倒數時發出提示音"</string>
+    <string name="setting_off" msgid="4480039384202951946">"關閉"</string>
+    <string name="setting_on" msgid="8602246224465348901">"開啟"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"影片品質"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"高畫質"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"低畫質"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"延時攝影"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"相機設定"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"攝錄影機設定"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"相片大小"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"1300 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"800 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"500 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"400 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"300 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"200 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"200 萬像素 (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"130 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"100 萬像素"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"對焦模式"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"自動"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"無限遠"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"微距"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"自動"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"無限遠"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"微距"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"閃光模式"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"閃光燈模式"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"自動"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"開啟"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"關閉"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"自動閃光燈"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"開啟閃光燈"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"關閉閃光燈"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"白平衡"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"白平衡"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"自動"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"鎢絲燈"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"日光"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"螢光燈"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"陰天"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"自動"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"鎢絲燈"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"日光"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"螢光燈"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"陰天"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"場景模式"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"自動"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"動態"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"夜景"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"黃昏"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"派對模式"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"無"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"動態"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"夜景"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"黃昏"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"派對"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"倒數計時器"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"計時器已關閉"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 秒"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 秒"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 秒"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 秒"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"在場景模式中無法選取。"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"曝光"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"曝光"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"高動態範圍"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"前置鏡頭"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"後置鏡頭"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"確定"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"您的 USB 儲存裝置的空間即將用盡,請變更圖片的品質設定,或是刪除部分圖片或其他檔案。"</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"您 SD 卡的空間即將不足,請變更圖片的品質設定,或是刪除部分圖片或其他檔案。"</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"已達大小上限。"</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"速度過快"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"正在準備全景預覽"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"無法儲存全景。"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"全景"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"全景拍攝中"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"正在等待先前的全景處理完成"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"儲存中…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"正在進行全景成像作業"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"輕觸即可對焦。"</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"效果"</string>
+    <string name="effect_none" msgid="3601545724573307541">"無"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"擠眉弄眼"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"大眼睛"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"大嘴巴"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"小嘴巴"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"大鼻子"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"小眼睛"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"太空"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"黃昏"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"您的影片"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"放下您的裝置"\n"暫時離開畫面。"</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"輕觸即可在錄製期間拍照。"</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"錄影程序已啟動。"</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"錄影程序已暫停。"</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"特殊效果啟用時無法使用影片快照。"</string>
+    <string name="clear_effects" msgid="5485339175014139481">"清除效果"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"耍笨臉"</string>
+    <string name="effect_background" msgid="6579360207378171022">"背景"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"[快門] 按鈕"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"選單按鈕"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"最近的相片"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"前置和後置鏡頭開關"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"相機、影片或全景選取工具"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"更多設定控制"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"關閉設定控制"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"縮放控制"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"縮小 %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"放大 %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s 核取方塊"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"切換至相片模式"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"切換至影片模式"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"切換至全景模式"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"切換為新全景"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"檢閱取消"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"檢閱完成"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"檢查重拍"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"播放影片"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"暫停影片"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"重新載入影片"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"影片播放器時間軸"</string>
+    <string name="capital_on" msgid="5491353494964003567">"開啟"</string>
+    <string name="capital_off" msgid="7231052688467970897">"關閉"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"關閉"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 秒"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 分鐘"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 小時"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 小時"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"秒"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"分鐘"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"小時"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"完成"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"設定時間間隔"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"延時攝影功能已關閉,請開啟以設定時間間格。"</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"倒數計時器目前為關閉狀態,開啟即可在拍照前倒數計時。"</string>
+    <string name="set_duration" msgid="5578035312407161304">"設定倒數時間 (以秒為單位)"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"拍照倒數計時中"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"記錄拍攝地點?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"為您的相片和影片標記拍攝地點。"\n\n"其他應用程式可存取這項資訊及您所儲存的相片。"</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"不用了,謝謝"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"是"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"相機"</string>
+    <string name="menu_search" msgid="7580008232297437190">"搜尋"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"相片"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"相簿"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"更多選項"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"設定"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d 張相片"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d 張相片"</item>
+  </plurals>
 </resources>
diff --git a/res/values-zu/filtershow_strings.xml b/res/values-zu/filtershow_strings.xml
index 18d1c69..93e04d2 100644
--- a/res/values-zu/filtershow_strings.xml
+++ b/res/values-zu/filtershow_strings.xml
@@ -20,21 +20,24 @@
     <string name="cannot_load_image" msgid="5023634941212959976">"Ayikwazi ukulayisha isithombe!"</string>
     <!-- no translation found for original_picture_text (3076213290079909698) -->
     <skip />
+    <string name="setting_wallpaper" msgid="4679087092300036632">"Isetha isithombe sangemuva"</string>
+    <string name="download_failure" msgid="5923323939788582895">"Ayikwazanga ukulanda isithombe. Inethiwekhi ayitholakali."</string>
     <string name="original" msgid="3524493791230430897">"Oluqobo"</string>
     <string name="borders" msgid="2067345080568684614">"Imingcele"</string>
-    <string name="done" msgid="3112344807927554662">"Kwenziwe"</string>
     <string name="filtershow_undo" msgid="6781743189243585101">"Hlehlisa"</string>
     <string name="filtershow_redo" msgid="4219489910543059747">"Yenza kabusha"</string>
-    <string name="show_history_panel" msgid="7785810372502120090">"Bonisa umlando"</string>
-    <string name="hide_history_panel" msgid="2082672248771133871">"Fihla umlando"</string>
-    <string name="show_imagestate_panel" msgid="7132294085840948243">"Bonisa isimo sesithombe"</string>
-    <string name="hide_imagestate_panel" msgid="1135313661068111161">"Fihla isimo sesithombe"</string>
+    <string name="show_imagestate_panel" msgid="281932769701043015">"Bonisa imiphumela esetshenzisiwe"</string>
+    <string name="hide_imagestate_panel" msgid="7207643485811695257">"Fihla imiphumela esetshenzisiwe"</string>
     <string name="menu_settings" msgid="6428291655769260831">"Izilungiselelo"</string>
+    <string name="unsaved" msgid="8704442449002374375">"Kukhona ushintsho olungalondolozwanga kulesi sithombe."</string>
+    <string name="save_before_exit" msgid="2680660633675916712">"Ufuna ukulondoloza ngaphambi kokuphuma?"</string>
+    <string name="save_and_exit" msgid="3628425023766687419">"Londoloza uphinde uphume"</string>
+    <string name="exit" msgid="242642957038770113">"Phuma"</string>
     <string name="history" msgid="455767361472692409">"Umlando"</string>
     <string name="reset" msgid="9013181350779592937">"Setha kabusha"</string>
     <!-- no translation found for history_original (150973253194312841) -->
     <skip />
-    <string name="imageState" msgid="3609930035023754855">"Isimo sesithombe samanje"</string>
+    <string name="imageState" msgid="8632586742752891968">"Imiphumela esetshenzisiwe"</string>
     <string name="compare_original" msgid="8140838959007796977">"Qhathanisa"</string>
     <string name="apply_effect" msgid="1218288221200568947">"Sebenzisa"</string>
     <string name="reset_effect" msgid="7712605581024929564">"Setha kabusha"</string>
@@ -49,6 +52,7 @@
     <string name="aspectNone_effect" msgid="6263330561046574134">"Akunalutho"</string>
     <!-- no translation found for aspectOriginal_effect (5678516555493036594) -->
     <skip />
+    <string name="Fixed" msgid="8017376448916924565">"Okungashintshi"</string>
     <string name="tinyplanet" msgid="2783694326474415761">"Inkanyezi ezungeza ilanga encane"</string>
     <string name="exposure" msgid="6526397045949374905">"Ukuboniswa"</string>
     <string name="sharpness" msgid="6463103068318055412">"Ubukhali"</string>
@@ -59,16 +63,35 @@
     <string name="wbalance" msgid="6346581563387083613">"I-Autocolor"</string>
     <string name="hue" msgid="6231252147971086030">"I-Hue"</string>
     <string name="shadow_recovery" msgid="3928572915300287152">"Izithunzi"</string>
+    <string name="highlight_recovery" msgid="8262208470735204243">"Okubekwe obala"</string>
     <string name="curvesRGB" msgid="915010781090477550">"Ukugobeka"</string>
     <string name="vignette" msgid="934721068851885390">"I-Vignette"</string>
     <string name="redeye" msgid="4508883127049472069">"Iso elibomvu"</string>
+    <string name="imageDraw" msgid="6918552177844486656">"Dweba"</string>
     <string name="straighten" msgid="26025591664983528">"Qondisa"</string>
     <string name="crop" msgid="5781263790107850771">"Sika"</string>
     <string name="rotate" msgid="2796802553793795371">"Phendula"</string>
     <string name="mirror" msgid="5482518108154883096">"Isibuko"</string>
+    <string name="negative" msgid="6998313764388022201">"Inegethivu"</string>
     <string name="none" msgid="6633966646410296520">"Akunalutho"</string>
+    <string name="edge" msgid="7036064886242147551">"Imiphetho"</string>
+    <string name="kmeans" msgid="1630263230946107457">"I-Warhol"</string>
+    <string name="downsample" msgid="3552938534146980104">"I-Downsample"</string>
     <string name="curves_channel_rgb" msgid="7909209509638333690">"I-RGB"</string>
     <string name="curves_channel_red" msgid="4199710104162111357">"Okubomvu"</string>
     <string name="curves_channel_green" msgid="3733003466905031016">"Okuluhlaza"</string>
     <string name="curves_channel_blue" msgid="9129211507395079371">"Okuluhlaza sasibhakabhaka"</string>
+    <string name="draw_style" msgid="2036125061987325389">"Isitayela"</string>
+    <string name="draw_size" msgid="4360005386104151209">"Usayizi"</string>
+    <string name="draw_color" msgid="2119030386987211193">"Umbala"</string>
+    <string name="draw_style_line" msgid="9216476853904429628">"Imigqa"</string>
+    <string name="draw_style_brush_spatter" msgid="7612691122932981554">"Isiphawuli"</string>
+    <string name="draw_style_brush_marker" msgid="8468302322165644292">"I-Spatter"</string>
+    <string name="draw_clear" msgid="6728155515454921052">"Sula"</string>
+    <string name="color_pick_select" msgid="734312818059057394">"Khetha umbala wangokwezifiso"</string>
+    <string name="color_pick_title" msgid="6195567431995308876">"Khetha umbala"</string>
+    <string name="draw_size_title" msgid="3121649039610273977">"Khetha usayizi"</string>
+    <string name="draw_size_accept" msgid="6781529716526190028">"KULUNGILE"</string>
+    <string name="state_panel_original" msgid="9069584409934164419">"Okwangempela"</string>
+    <string name="state_panel_result" msgid="318640531123298676">"Umphumela"</string>
 </resources>
diff --git a/res/values-zu/photoeditor_strings.xml b/res/values-zu/photoeditor_strings.xml
deleted file mode 100644
index e63d2a8..0000000
--- a/res/values-zu/photoeditor_strings.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--  Copyright (C) 2010 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.
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="photoeditor_name" msgid="4738587346771969697">"Endawo Yezithombe"</string>
-    <string name="loading_failure" msgid="7890360277163832130">"Ayikwazanga ukulayisha isithombe"</string>
-    <string name="saving_failure" msgid="8229491575433743974">"Ayikwazanga ukulondoloza isithombe esilungisiwe"</string>
-    <string name="photo_saved" msgid="6163006724627682202">"Isithombe esilungisiwe silondolozwe e <xliff:g id="FOLDER_NAME">%s</xliff:g>"</string>
-    <string name="discard_unsaved_photo" msgid="2353274490633681946">"Lahla izinguquko ezingagciniwe?"</string>
-    <string name="yes" msgid="5402582493291792293">"Yebo"</string>
-    <string name="save" msgid="5516670392524294967">"Londoloza"</string>
-    <string name="autofix" msgid="1663414996270538748">"Insiza Yokulungisa Okuzenzekelayo"</string>
-    <string name="crop" msgid="7598378507763334041">"Nqampuna"</string>
-    <string name="crossprocess" msgid="4173724489001742342">"inqubo-eshayisanayo"</string>
-    <string name="documentary" msgid="50396326708699797">"Idokhumentari"</string>
-    <string name="doodle" msgid="1686409894518940990">"Umdwebo oshintshayo"</string>
-    <string name="duotone" msgid="8145893940788467106">"ithoni-engabili"</string>
-    <string name="facelift" msgid="6205748523156172637">"I-Face Glow"</string>
-    <string name="facetan" msgid="4412831806626044111">"I-Face Tan"</string>
-    <string name="filllight" msgid="2644989991700022526">"Faka Ukukhanya"</string>
-    <string name="fisheye" msgid="6037488646928998921">"Isolenhlanzi"</string>
-    <string name="flip" msgid="2357692401826287480">"Phendula"</string>
-    <string name="grain" msgid="7487585304579789098">"Inhlamvu Yefilimu"</string>
-    <string name="grayscale" msgid="7641563843060945228">"i-B&amp;W"</string>
-    <string name="accessibility_black_and_white" msgid="1156382046161392389">"Okumnyama nokumhlophe"</string>
-    <string name="highlight" msgid="3902653944386623972">"Amazwibela"</string>
-    <string name="lomoish" msgid="1270032154357186736">"i-Lomo"</string>
-    <string name="negative" msgid="1985508917342811252">"Akulungile"</string>
-    <string name="posterize" msgid="4139212359561383385">"Ukumisaphuhle"</string>
-    <string name="redeye" msgid="4958448806369928239">"Iso Elibomvu"</string>
-    <string name="rotate" msgid="6607597269792373083">"Phendula"</string>
-    <string name="saturation" msgid="8621322012271169931">"Gcwalisa isikhala"</string>
-    <string name="sepia" msgid="7978093531824705601">"I-Sepia"</string>
-    <string name="shadow" msgid="8235188588101973090">"Izithunzi"</string>
-    <string name="sharpen" msgid="8449662378104403230">"Yenza bukhali"</string>
-    <string name="straighten" msgid="5217801513491493491">"Qondisa"</string>
-    <string name="temperature" msgid="1607987938521534517">"Ukufudumala"</string>
-    <string name="tint" msgid="154435943863418434">"Thikabeza umbala"</string>
-    <string name="vignette" msgid="7648125924662648282">"i-Vignette"</string>
-    <string name="crop_tooltip" msgid="8794037869706891710">"Hudula insiza yokubeka uphawu ukuze usike ongakudingi esithombeni"</string>
-    <string name="doodle_tooltip" msgid="2902117272374362915">"Dweba esithombeni ukuze kube nemidwebo eshintshayo"</string>
-    <string name="flip_tooltip" msgid="2700943256714731737">"Hudula isithombe ukuze usiphendule"</string>
-    <string name="redeye_tooltip" msgid="4183695928490874014">"Thinta emehlweni abomvu ukuze uwasuse"</string>
-    <string name="rotate_tooltip" msgid="7008602969130734229">"Hudula isithombe ukuze usijikeleze"</string>
-    <string name="straighten_tooltip" msgid="4846317027139212339">"Hudula isithombe ukuze usiqondise"</string>
-    <string name="photoeditor_exposure" msgid="560535856805042987">"Imiphumela yokuvela"</string>
-    <string name="photoeditor_color" msgid="834210916165605468">"Imiphumela yombala"</string>
-    <string name="photoeditor_artistic" msgid="2810641816416597464">"Imiphumela yobuciko"</string>
-    <string name="photoeditor_fix" msgid="6441595584425037477">"Lungisa"</string>
-    <string name="photoeditor_undo" msgid="4831462549862253564">"Hlehlisa"</string>
-    <string name="photoeditor_redo" msgid="5999984591376273768">"Yenza kabusha"</string>
-</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 78f7585..030ab22 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -90,11 +90,14 @@
     <string name="rotate_right" msgid="6776325835923384839">"Phendukisela kwesokudla"</string>
     <string name="no_such_item" msgid="5315144556325243400">"Yehlulekile ukuthola into yakho."</string>
     <string name="edit" msgid="1502273844748580847">"Hlela"</string>
+    <string name="simple_edit" msgid="2792835918347498211">"Ukuhlela okulula"</string>
     <string name="process_caching_requests" msgid="8722939570307386071">"Ilungisa izicelo zokufaka kwinqolobane"</string>
     <string name="caching_label" msgid="4521059045896269095">"Ukulondoloza isikhashana..."</string>
     <string name="crop_action" msgid="3427470284074377001">"Khropha"</string>
     <string name="trim_action" msgid="703098114452883524">"Lungisa"</string>
+    <string name="mute_action" msgid="5296241754753306251">"Thulisa"</string>
     <string name="set_as" msgid="3636764710790507868">"Hlela njenge"</string>
+    <string name="video_mute_err" msgid="6392457611270600908">"Ayikwazi ukuthulisa ividiyo."</string>
     <string name="video_err" msgid="7003051631792271009">"Ayikwazi ukudlala ividiyo"</string>
     <string name="group_by_location" msgid="316641628989023253">"Ngendawo"</string>
     <string name="group_by_time" msgid="9046168567717963573">"Ngesikhathi"</string>
@@ -143,6 +146,7 @@
     <string name="auto" msgid="4296941368722892821">"Okuzenzakalelayo"</string>
     <string name="flash_on" msgid="7891556231891837284">"Ifuleshi iqhafaziwe"</string>
     <string name="flash_off" msgid="1445443413822680010">"Ayikho ifuleshi"</string>
+    <string name="unknown" msgid="3506693015896912952">"Akwaziwa"</string>
     <string name="ffx_original" msgid="372686331501281474">"Oluqobo"</string>
     <string name="ffx_vintage" msgid="8348759951363844780">"I-Vintage"</string>
     <string name="ffx_instant" msgid="726968618715691987">"Ngokuzenzakalela"</string>
@@ -195,10 +199,243 @@
     <string name="no_external_storage" msgid="95726173164068417">"Asikho isilondolozi sangaphandle esikhona"</string>
     <string name="switch_photo_filmstrip" msgid="8227883354281661548">"Ukubuka nge-Filmstrip"</string>
     <string name="switch_photo_grid" msgid="3681299459107925725">"Ukubuka ngegridi"</string>
+    <string name="switch_photo_fullscreen" msgid="8360489096099127071">"Ukubukwa kwesikrini esigcwele"</string>
     <string name="trimming" msgid="9122385768369143997">"Ukusika"</string>
+    <string name="muting" msgid="5094925919589915324">"Ukuthulisa"</string>
     <string name="please_wait" msgid="7296066089146487366">"Sicela ulinde"</string>
-    <string name="save_into" msgid="4960537214388766062">"Ukulondoloza ividiyo esikiwe ku-albhamu:"</string>
+    <string name="save_into" msgid="9155488424829609229">"Kulondolozwa ividiyo ku-<xliff:g id="ALBUM_NAME">%1$s</xliff:g> …"</string>
     <string name="trim_too_short" msgid="751593965620665326">"Awukwazi ukusika : ividiyo eqondisiwe yifushane kakhulu"</string>
     <string name="pano_progress_text" msgid="1586851614586678464">"Ukufaka i-panorama"</string>
     <string name="save" msgid="613976532235060516">"Londoloza"</string>
+    <string name="ingest_scanning" msgid="1062957108473988971">"Kuskenwa okuqukethwe..."</string>
+  <plurals name="ingest_number_of_items_scanned">
+    <item quantity="zero" msgid="2623289390474007396">"%1$d izinto eziskeniwe"</item>
+    <item quantity="one" msgid="4340019444460561648">"%1$d into eskeniwe"</item>
+    <item quantity="other" msgid="3138021473860555499">"%1$d izinto eziskeniwe"</item>
+  </plurals>
+    <string name="ingest_sorting" msgid="1028652103472581918">"Kuyahlungwa..."</string>
+    <string name="ingest_scanning_done" msgid="8911916277034483430">"Ukuskena kuqediwe"</string>
+    <string name="ingest_importing" msgid="7456633398378527611">"Iyangenisa..."</string>
+    <string name="ingest_empty_device" msgid="2010470482779872622">"Akukho okuqukethwe okutholakalela ukungeniswa kule divayisi."</string>
+    <string name="ingest_no_device" msgid="3054128223131382122">"Ayikho idivayisi ye-MTP exhunyiwe"</string>
+    <string name="camera_error_title" msgid="6484667504938477337">"Iphutha lekhamera"</string>
+    <string name="cannot_connect_camera" msgid="955440687597185163">"Ayikwazi ukuxhuma ekhamereni."</string>
+    <string name="camera_disabled" msgid="8923911090533439312">"Ikhamera inqunyiwe ngenxa yepolisi yezokuphepha."</string>
+    <string name="camera_label" msgid="6346560772074764302">"Ikhamera"</string>
+    <string name="video_camera_label" msgid="2899292505526427293">"Ikhamera enerekhoda"</string>
+    <string name="wait" msgid="8600187532323801552">"Sicela ulinde..."</string>
+    <string name="no_storage" product="nosdcard" msgid="7335975356349008814">"Sicela ukhweze isitoreji se-USB ngaphambi kokusebenzisa ikhamera."</string>
+    <string name="no_storage" product="default" msgid="5137703033746873624">"Sicela ufake ikhadi le-SD ngaphambi kokusebenzisa ikhamera."</string>
+    <string name="preparing_sd" product="nosdcard" msgid="6104019983528341353">"Ilungiselela isitoreji se-USB..."</string>
+    <string name="preparing_sd" product="default" msgid="2914969119574812666">"Ilungiselela ikhadi le-SD..."</string>
+    <string name="access_sd_fail" product="nosdcard" msgid="8147993984037859354">"Yehlulekile ukufinyelela kwindawo egcina i-USB."</string>
+    <string name="access_sd_fail" product="default" msgid="1584968646870054352">"Yehlukekile ukufinyelela kwikhadi le-SD."</string>
+    <string name="review_cancel" msgid="8188009385853399254">"KHANSELA"</string>
+    <string name="review_ok" msgid="1156261588693116433">"KUQEDIWE"</string>
+    <string name="time_lapse_title" msgid="4360632427760662691">"Iqopha ukuphela kwesikhathi"</string>
+    <string name="pref_camera_id_title" msgid="4040791582294635851">"Khetha ikhamera"</string>
+    <string name="pref_camera_id_entry_back" msgid="5142699735103692485">"Emuva"</string>
+    <string name="pref_camera_id_entry_front" msgid="5668958706828733669">"Phambili"</string>
+    <string name="pref_camera_recordlocation_title" msgid="371208839215448917">"Gcina indawo"</string>
+    <string name="pref_camera_location_label" msgid="2254270920298609161">"INDAWO"</string>
+    <string name="pref_camera_timer_title" msgid="3105232208281893389">"Isikali sesikhathi esibala ngokwehla"</string>
+  <plurals name="pref_camera_timer_entry">
+    <item quantity="one" msgid="1654523400981245448">"1 isekhondi"</item>
+    <item quantity="other" msgid="6455381617076792481">"%d amasekhondi"</item>
+  </plurals>
+    <!-- no translation found for pref_camera_timer_sound_default (7066624532144402253) -->
+    <skip />
+    <string name="pref_camera_timer_sound_title" msgid="2469008631966169105">"Bhipha ngesikhathi sokubala ngokwehla"</string>
+    <string name="setting_off" msgid="4480039384202951946">"Kucishile"</string>
+    <string name="setting_on" msgid="8602246224465348901">"Vuliwe"</string>
+    <string name="pref_video_quality_title" msgid="8245379279801096922">"Ikhwalithi yevidiyo"</string>
+    <string name="pref_video_quality_entry_high" msgid="8664038216234805914">"Phezulu"</string>
+    <string name="pref_video_quality_entry_low" msgid="7258507152393173784">"Phansi"</string>
+    <string name="pref_video_time_lapse_frame_interval_title" msgid="6245716906744079302">"Ukudlula kwesikhathi"</string>
+    <string name="pref_camera_settings_category" msgid="2576236450859613120">"Izilungiselelo zekhamera"</string>
+    <string name="pref_camcorder_settings_category" msgid="460313486231965141">"Izilungiselelo zekhamera enerekhoda"</string>
+    <string name="pref_camera_picturesize_title" msgid="4333724936665883006">"Usayizi wesithombe"</string>
+    <string name="pref_camera_picturesize_entry_13mp" msgid="675309554194481780">"13M amaphikiseli"</string>
+    <string name="pref_camera_picturesize_entry_8mp" msgid="259953780932849079">"8M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_5mp" msgid="2882928212030661159">"5M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_4mp" msgid="933242108272469142">"4M amaphikiseli"</string>
+    <string name="pref_camera_picturesize_entry_3mp" msgid="741415860337400696">"3M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_2mp" msgid="1753709802245460393">"2M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_2mp_wide" msgid="5359490802026616612">"2M amaphikiseli (16:9)"</string>
+    <string name="pref_camera_picturesize_entry_1_3mp" msgid="829109608140747258">"1.3M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_1mp" msgid="1669725616780375066">"1M amaphikseli"</string>
+    <string name="pref_camera_picturesize_entry_vga" msgid="806934254162981919">"VGA"</string>
+    <string name="pref_camera_picturesize_entry_qvga" msgid="8576186463069770133">"QVGA"</string>
+    <string name="pref_camera_focusmode_title" msgid="2877248921829329127">"Imodi yefokhasi"</string>
+    <string name="pref_camera_focusmode_entry_auto" msgid="7374820710300362457">"Okuzenzakalelayo"</string>
+    <string name="pref_camera_focusmode_entry_infinity" msgid="3413922419264967552">"Kwaphakade"</string>
+    <string name="pref_camera_focusmode_entry_macro" msgid="4424489110551866161">"Imakhro"</string>
+    <string name="pref_camera_focusmode_label_auto" msgid="8547956917516317183">"OKUZENZAKALELAYO"</string>
+    <string name="pref_camera_focusmode_label_infinity" msgid="4272904160062531778">"KWAPHAKADE"</string>
+    <string name="pref_camera_focusmode_label_macro" msgid="8749317592620908054">"IMAKHRO"</string>
+    <string name="pref_camera_flashmode_title" msgid="2287362477238791017">"Imodi yokufulesh"</string>
+    <string name="pref_camera_flashmode_label" msgid="7546741624882856171">"IMODI YEFLESHI"</string>
+    <string name="pref_camera_flashmode_entry_auto" msgid="7288383434237457709">"Okuzenzakalelayo"</string>
+    <string name="pref_camera_flashmode_entry_on" msgid="5330043918845197616">"Vuliwe"</string>
+    <string name="pref_camera_flashmode_entry_off" msgid="867242186958805761">"Valiwe"</string>
+    <string name="pref_camera_flashmode_label_auto" msgid="8854671890619026197">"UKUZENZAKALELA KWEFLESHI"</string>
+    <string name="pref_camera_flashmode_label_on" msgid="7347504762794840140">"IFLESHI IVULIWE"</string>
+    <string name="pref_camera_flashmode_label_off" msgid="3541596735596053416">"IFLESHI IVALIWE"</string>
+    <string name="pref_camera_whitebalance_title" msgid="677420930596673340">"Bhalansa ubumhlophe"</string>
+    <string name="pref_camera_whitebalance_label" msgid="7467403405883190920">"UKULINGANA KOKUMHLOPHE"</string>
+    <string name="pref_camera_whitebalance_entry_auto" msgid="6580665476983469293">"Okuzenzakalelayo"</string>
+    <string name="pref_camera_whitebalance_entry_incandescent" msgid="8856667786449549938">"Mhlophe komlilo"</string>
+    <string name="pref_camera_whitebalance_entry_daylight" msgid="2534757270149561027">"Emini"</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent" msgid="2435332872847454032">"Ukukhanya okumibalabala"</string>
+    <string name="pref_camera_whitebalance_entry_cloudy" msgid="3531996716997959326">"Kunamafu"</string>
+    <string name="pref_camera_whitebalance_label_auto" msgid="1479694362310491429">"OKUZENZAKALELAYO"</string>
+    <string name="pref_camera_whitebalance_label_incandescent" msgid="7427628260209908900">"MHLOPHE KOMLILO"</string>
+    <string name="pref_camera_whitebalance_label_daylight" msgid="1859710806141461399">"EMINI"</string>
+    <string name="pref_camera_whitebalance_label_fluorescent" msgid="5173251749161337707">"UKUKHANYA OKUMIBALABALA"</string>
+    <string name="pref_camera_whitebalance_label_cloudy" msgid="8230173517179285320">"KUNAMAFU"</string>
+    <string name="pref_camera_scenemode_title" msgid="1420535844292504016">"Imodi yesigcawu"</string>
+    <string name="pref_camera_scenemode_entry_auto" msgid="7113995286836658648">"Okuzenzakalelayo"</string>
+    <string name="pref_camera_scenemode_entry_hdr" msgid="2923388802899511784">"i-HDR"</string>
+    <string name="pref_camera_scenemode_entry_action" msgid="616748587566110484">"Isenzo"</string>
+    <string name="pref_camera_scenemode_entry_night" msgid="7606898503102476329">"Ebusuku"</string>
+    <string name="pref_camera_scenemode_entry_sunset" msgid="181661154611507212">"Ukushona kwelanga"</string>
+    <string name="pref_camera_scenemode_entry_party" msgid="907053529286788253">"Phathi"</string>
+    <string name="pref_camera_scenemode_label_auto" msgid="4475096836397300237">"LUTHO"</string>
+    <string name="pref_camera_scenemode_label_action" msgid="964748409622151496">"ISENZO"</string>
+    <string name="pref_camera_scenemode_label_night" msgid="1269871886845854574">"EBUSUKU"</string>
+    <string name="pref_camera_scenemode_label_sunset" msgid="2802732082948866877">"UKUSHONA KWELANGA"</string>
+    <string name="pref_camera_scenemode_label_party" msgid="1409459091844374828">"IPHATHI"</string>
+    <string name="pref_camera_countdown_label" msgid="7592784692450586126">"ISIBALI SESHIKHATHI ESIBALA NGOKWEHLA"</string>
+    <string name="pref_camera_countdown_label_off" msgid="4987856883590176585">"ISIKALI SESIKHATHI SIVALIWE"</string>
+    <string name="pref_camera_countdown_label_one" msgid="1101814103087928898">"1 ISEKHONDI"</string>
+    <string name="pref_camera_countdown_label_three" msgid="1047399297342955649">"3 AMASEKHONDI"</string>
+    <string name="pref_camera_countdown_label_ten" msgid="6274681535347260279">"10 AMASEKHONDI"</string>
+    <string name="pref_camera_countdown_label_fifteen" msgid="4544824246687597089">"15 AMASEKHONDI"</string>
+    <string name="not_selectable_in_scene_mode" msgid="2970291701448555126">"Akukhetheki esimweni sokubuka"</string>
+    <string name="pref_exposure_title" msgid="1229093066434614811">"Isibonelelo"</string>
+    <string name="pref_exposure_label" msgid="552624394642497940">"UKUBONISWA"</string>
+    <!-- no translation found for pref_camera_hdr_default (1336869406134365882) -->
+    <skip />
+    <string name="pref_camera_hdr_label" msgid="7217211253357027510">"I-HDR"</string>
+    <string name="pref_camera_id_label_back" msgid="8745553500400332333">"IKHAMERA EPHAMBILI"</string>
+    <string name="pref_camera_id_label_front" msgid="8699439330056996709">"IKHAMERA ENGEMUVA"</string>
+    <string name="dialog_ok" msgid="6263301364153382152">"KULUNGILE"</string>
+    <string name="spaceIsLow_content" product="nosdcard" msgid="4401325203349203177">"Isitoreji se-USB yakho siphelelwa yisikhala. Shintsha ilungiselelo lekhwalithi noma susa ezinye izithombe noma amanye amafayela."</string>
+    <string name="spaceIsLow_content" product="default" msgid="1732882643101247179">"Ikhadi lakho le-SD liphelelwa isikhathi. Shintsha ilungiselelo lekhwalithi noma susa eminye imifanekiso noma amanye amafayela."</string>
+    <string name="video_reach_size_limit" msgid="6179877322015552390">"Umkhwawulo wosayizi ufinyelelwe."</string>
+    <string name="pano_too_fast_prompt" msgid="2823839093291374709">"Ishesha Kakhulu"</string>
+    <string name="pano_dialog_prepare_preview" msgid="4788441554128083543">"Ilungiselela i-panorama"</string>
+    <string name="pano_dialog_panorama_failed" msgid="2155692796549642116">"Yehlulekile ukulondoloza i-panaroma"</string>
+    <string name="pano_dialog_title" msgid="5755531234434437697">"I-Panorama"</string>
+    <string name="pano_capture_indication" msgid="8248825828264374507">"Ilondoloza i-Panorama"</string>
+    <string name="pano_dialog_waiting_previous" msgid="7800325815031423516">"Ilinde i-panaroma yangaphambilini"</string>
+    <string name="pano_review_saving_indication_str" msgid="2054886016665130188">"Iyalondoloza…"</string>
+    <string name="pano_review_rendering" msgid="2887552964129301902">"Kufakwa i-panorama"</string>
+    <string name="tap_to_focus" msgid="8863427645591903760">"Thinta ukuze kume kahle isithombe."</string>
+    <string name="pref_video_effect_title" msgid="8243182968457289488">"Imithelela"</string>
+    <string name="effect_none" msgid="3601545724573307541">"Lutho"</string>
+    <string name="effect_goofy_face_squeeze" msgid="1207235692524289171">"Shutheka"</string>
+    <string name="effect_goofy_face_big_eyes" msgid="3945182409691408412">"Amehlo amakhulu"</string>
+    <string name="effect_goofy_face_big_mouth" msgid="7528748779754643144">"Umlomo Omkhulu"</string>
+    <string name="effect_goofy_face_small_mouth" msgid="3848209817806932565">"Umlomo Omncane"</string>
+    <string name="effect_goofy_face_big_nose" msgid="5180533098740577137">"Ikhala Elikhulu"</string>
+    <string name="effect_goofy_face_small_eyes" msgid="1070355596290331271">"Amehlo Amancane"</string>
+    <string name="effect_backdropper_space" msgid="7935661090723068402">"Eskhaleni"</string>
+    <string name="effect_backdropper_sunset" msgid="45198943771777870">"Ukushona kwelanga"</string>
+    <string name="effect_backdropper_gallery" msgid="959158844620991906">"Ividiyo yakho"</string>
+    <string name="bg_replacement_message" msgid="9184270738916564608">"Setha idivayisi yakho phansi."\n"Isethe ingabonakali okwesikhashana."</string>
+    <string name="video_snapshot_hint" msgid="18833576851372483">"Thinta ukuze uthwebule isithombe ngenkathi uqopha."</string>
+    <string name="video_recording_started" msgid="4132915454417193503">"Ukuqoshwa kwevidiyo sekuqalile."</string>
+    <string name="video_recording_stopped" msgid="5086919511555808580">"Ukuqoshwa kwevidiyo sekumisiwe."</string>
+    <string name="disable_video_snapshot_hint" msgid="4957723267826476079">"Umfanekiso wevidyo awusebenzi uma izinandisi ezikhethekile zivuliwe."</string>
+    <string name="clear_effects" msgid="5485339175014139481">"Izinandisi ezicacile"</string>
+    <string name="effect_silly_faces" msgid="8107732405347155777">"UBUSO OBUNGASILE"</string>
+    <string name="effect_background" msgid="6579360207378171022">"INGEMUVA"</string>
+    <string name="accessibility_shutter_button" msgid="2664037763232556307">"Inkinobho Yokuvala"</string>
+    <string name="accessibility_menu_button" msgid="7140794046259897328">"Inkinobho yemenyu"</string>
+    <string name="accessibility_review_thumbnail" msgid="8961275263537513017">"Isithombe sakamuva"</string>
+    <string name="accessibility_camera_picker" msgid="8807945470215734566">"Iswishi yekhamera yangaphambili kanye nangemuva"</string>
+    <string name="accessibility_mode_picker" msgid="3278002189966833100">"Ikhamera, ividiyo noma ukhetho lwe-panorama"</string>
+    <string name="accessibility_second_level_indicators" msgid="3855951632917627620">"Izilawuli zezilungiselelo ezingaphezulu"</string>
+    <string name="accessibility_back_to_first_level" msgid="5234411571109877131">"Vala izilawulo zezilungiso"</string>
+    <string name="accessibility_zoom_control" msgid="1339909363226825709">"Ulawulo lokulwiza"</string>
+    <string name="accessibility_decrement" msgid="1411194318538035666">"Nciphisa %1$s"</string>
+    <string name="accessibility_increment" msgid="8447850530444401135">"Yandisa %1$s"</string>
+    <string name="accessibility_check_box" msgid="7317447218256584181">"%1$s ibhokisi lokuhlola"</string>
+    <string name="accessibility_switch_to_camera" msgid="5951340774212969461">"Shintshela esithombeni"</string>
+    <string name="accessibility_switch_to_video" msgid="4991396355234561505">"Shintshela kwividiyo"</string>
+    <string name="accessibility_switch_to_panorama" msgid="604756878371875836">"Shintshela kwi-panorama"</string>
+    <string name="accessibility_switch_to_new_panorama" msgid="8116783308051524188">"Shintshela ku-panorama entsha"</string>
+    <string name="accessibility_review_cancel" msgid="9070531914908644686">"Buyekeza ukukhansela"</string>
+    <string name="accessibility_review_ok" msgid="7793302834271343168">"Ukubuyekeza kuphelile"</string>
+    <string name="accessibility_review_retake" msgid="659300290054705484">"Ukuphindwa kuthathwe kwesibuyekezo"</string>
+    <string name="accessibility_play_video" msgid="7596298365794810207">"Dlala ividiyo"</string>
+    <string name="accessibility_pause_video" msgid="6526344477133046653">"Misa ividiyo okwesikhashana"</string>
+    <string name="accessibility_reload_video" msgid="3250335917598607232">"Layisha kabusha ividiyo"</string>
+    <string name="accessibility_time_bar" msgid="1414029843602604531">"Ibha yesikhathi sesidlali sevidiyo"</string>
+    <string name="capital_on" msgid="5491353494964003567">"KUVULIWE"</string>
+    <string name="capital_off" msgid="7231052688467970897">"KUCINYIWE"</string>
+    <string name="pref_video_time_lapse_frame_interval_off" msgid="3490489191038309496">"Valiwe"</string>
+    <string name="pref_video_time_lapse_frame_interval_500" msgid="2949719376111679816">"0.5 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1000" msgid="1672458758823855874">"1 isekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1500" msgid="3415071702490624802">"1.5 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2000" msgid="827813989647794389">"2 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_2500" msgid="5750464143606788153">"2.5 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_3000" msgid="2664846627499751396">"3 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_4000" msgid="7303255804306382651">"4 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_5000" msgid="6800566761690741841">"5 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_6000" msgid="8545447466540319539">"6 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_10000" msgid="3105568489694909852">"10 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_12000" msgid="6055574367392821047">"12 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_15000" msgid="2656164845371833761">"15 amsekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_24000" msgid="2192628967233421512">"24 amasekhondi"</string>
+    <string name="pref_video_time_lapse_frame_interval_30000" msgid="5923393773260634461">"0.5 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_60000" msgid="4678581247918524850">"1 iminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_90000" msgid="1187029705069674152">"1.5 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_120000" msgid="145301938098991278">"2 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_150000" msgid="793707078196731912">"2.5 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_180000" msgid="1785467676466542095">"3 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_240000" msgid="3734507766184666356">"4 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_300000" msgid="7442765761995328639">"5 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_360000" msgid="6724596937972563920">"6 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_600000" msgid="6563665954471001352">"10 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_720000" msgid="8969801372893266408">"12 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_900000" msgid="5803172407245902896">"15 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1440000" msgid="6286246349698492186">"24 amaminithi"</string>
+    <string name="pref_video_time_lapse_frame_interval_1800000" msgid="5042628461448570758">"0.5 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_3600000" msgid="6366071632666482636">"1 ihora"</string>
+    <string name="pref_video_time_lapse_frame_interval_5400000" msgid="536117788694519019">"1.5 ihora"</string>
+    <string name="pref_video_time_lapse_frame_interval_7200000" msgid="6846617415182608533">"2 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_9000000" msgid="4242839574025261419">"2.5 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_10800000" msgid="2766886102170605302">"3 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_14400000" msgid="7497934659667867582">"4 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_18000000" msgid="8783643014853837140">"5 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_21600000" msgid="5005078879234015432">"6 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_36000000" msgid="69942198321578519">"10 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_43200000" msgid="285992046818504906">"12 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_54000000" msgid="5740227373848829515">"15 amahora"</string>
+    <string name="pref_video_time_lapse_frame_interval_86400000" msgid="9040201678470052298">"24 amahora"</string>
+    <string name="time_lapse_seconds" msgid="2105521458391118041">"amasekhondi"</string>
+    <string name="time_lapse_minutes" msgid="7738520349259013762">"amaminithi"</string>
+    <string name="time_lapse_hours" msgid="1776453661704997476">"amahora"</string>
+    <string name="time_lapse_interval_set" msgid="2486386210951700943">"Kwenziwe"</string>
+    <string name="set_time_interval" msgid="2970567717633813771">"Setha umkhawulo wesikhathi"</string>
+    <string name="set_time_interval_help" msgid="6665849510484821483">"Isici sesikhathi esidlulile sicimile. Sikhanyise ukuze usethe umkhawulo wesikhathi."</string>
+    <string name="set_timer_help" msgid="5007708849404589472">"Isikali sesikhathi esibala ngokwehla sivaliwe. Sivule ukuze ubale ngokwehla ngaphambi kokuthatha isithombe."</string>
+    <string name="set_duration" msgid="5578035312407161304">"Setha ubude besikhathi bube amasekhondi"</string>
+    <string name="count_down_title_text" msgid="4976386810910453266">"Kubalwa ngokwehla kuze kufike ekuthatheni isithombe"</string>
+    <string name="remember_location_title" msgid="9060472929006917810">"Khumbula izindawo zezithombe?"</string>
+    <string name="remember_location_prompt" msgid="724592331305808098">"Maka izithombe zakho namavidiyo ngezindawo lapha zithathwe khona."\n\n"Ezinye izinhlelo zokusebenza zingafinyelela lolu lwazi nezithombe zakho ezilondoloziwe."</string>
+    <string name="remember_location_no" msgid="7541394381714894896">"Cha ngiyabonga"</string>
+    <string name="remember_location_yes" msgid="862884269285964180">"Yebo"</string>
+    <string name="menu_camera" msgid="3476709832879398998">"Ikhamera"</string>
+    <string name="menu_search" msgid="7580008232297437190">"Sesha"</string>
+    <string name="tab_photos" msgid="9110813680630313419">"Izithombe"</string>
+    <string name="tab_albums" msgid="8079449907770685691">"Ama-albhamu"</string>
+    <string name="camera_menu_more_label" msgid="6868642182125198710">"IZINKETHO EZININGI"</string>
+    <string name="camera_menu_settings_label" msgid="875454962069404723">"IZILUNGISELELO"</string>
+  <plurals name="number_of_photos">
+    <item quantity="one" msgid="6949174783125614798">"%1$d isithombe"</item>
+    <item quantity="other" msgid="3813306834113858135">"%1$d izithombe"</item>
+  </plurals>
 </resources>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..5457650
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,489 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2008 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.
+ -->
+
+<resources>
+    <!-- Camera Preferences Video Quality entries -->
+    <string-array name="pref_video_quality_entries" translatable="false">
+        <item>@string/pref_video_quality_entry_1080p</item>
+        <item>@string/pref_video_quality_entry_720p</item>
+        <item>@string/pref_video_quality_entry_480p</item>
+        <item>@string/pref_video_quality_entry_high</item>
+        <item>@string/pref_video_quality_entry_low</item>
+   </string-array>
+
+    <string-array name="pref_video_quality_entryvalues" translatable="false">
+        <!-- The integer value of CamcorderProfile.QUALITY_1080P -->
+        <item>6</item>
+        <!-- The integer value of CamcorderProfile.QUALITY_720P -->
+        <item>@string/pref_video_quality_default</item>
+        <!-- The integer value of CamcorderProfile.QUALITY_480P -->
+        <item>4</item>
+        <!-- The integer value of CamcorderProfile.QUALITY_HIGH -->
+        <item>1</item>
+         <!-- The integer value of CamcorderProfile.QUALITY_LOW -->
+        <item>0</item>
+    </string-array>
+
+    <!-- These values correspond to the time interval between frame capture in millseconds
+    for time lapse recording -->
+    <string-array name="pref_video_time_lapse_frame_interval_entryvalues" translatable="false">
+        <item>0</item>
+        <item>500</item>
+        <item>1000</item>
+        <item>1500</item>
+        <item>2000</item>
+        <item>2500</item>
+        <item>3000</item>
+        <item>4000</item>
+        <item>5000</item>
+        <item>6000</item>
+        <item>10000</item>
+        <item>12000</item>
+        <item>15000</item>
+        <item>24000</item>
+        <item>30000</item>
+        <item>60000</item>
+        <item>90000</item>
+        <item>120000</item>
+        <item>150000</item>
+        <item>180000</item>
+        <item>240000</item>
+        <item>300000</item>
+        <item>360000</item>
+        <item>600000</item>
+        <item>720000</item>
+        <item>900000</item>
+        <item>1440000</item>
+        <item>1800000</item>
+        <item>3600000</item>
+        <item>5400000</item>
+        <item>7200000</item>
+        <item>9000000</item>
+        <item>10800000</item>
+        <item>14400000</item>
+        <item>18000000</item>
+        <item>21600000</item>
+        <item>36000000</item>
+        <item>43200000</item>
+        <item>54000000</item>
+        <item>86400000</item>
+    </string-array>
+
+    <!-- These values correspond to the time interval between frame capture in
+    different units (i.e. seconds, minutes, hours) for time lapse recording -->
+    <string-array name="pref_video_time_lapse_frame_interval_entries">
+        <item>@string/pref_video_time_lapse_frame_interval_off</item>
+        <item>@string/pref_video_time_lapse_frame_interval_500</item>
+        <item>@string/pref_video_time_lapse_frame_interval_1000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_1500</item>
+        <item>@string/pref_video_time_lapse_frame_interval_2000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_2500</item>
+        <item>@string/pref_video_time_lapse_frame_interval_3000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_4000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_5000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_6000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_10000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_12000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_15000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_24000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_30000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_60000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_90000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_120000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_150000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_180000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_240000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_300000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_360000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_600000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_720000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_900000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_1440000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_1800000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_3600000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_5400000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_7200000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_9000000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_10800000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_14400000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_18000000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_21600000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_36000000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_43200000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_54000000</item>
+        <item>@string/pref_video_time_lapse_frame_interval_86400000</item>
+    </string-array>
+
+    <!-- These values correspond to the time interval between frame capture
+    for time lapse recording -->
+    <string-array name="pref_video_time_lapse_frame_interval_duration_values" translatable="false">
+        <item>0.5</item>
+        <item>1</item>
+        <item>1.5</item>
+        <item>2</item>
+        <item>2.5</item>
+        <item>3</item>
+        <item>4</item>
+        <item>5</item>
+        <item>6</item>
+        <item>10</item>
+        <item>12</item>
+        <item>15</item>
+        <item>24</item>
+    </string-array>
+
+    <string-array name="pref_video_time_lapse_frame_interval_units">
+        <item>@string/time_lapse_seconds</item>
+        <item>@string/time_lapse_minutes</item>
+        <item>@string/time_lapse_hours</item>
+    </string-array>
+
+    <!-- Camera Preferences Picture size dialog box entries -->
+    <string-array name="pref_camera_picturesize_entries" translatable="false">
+        <item>@string/pref_camera_picturesize_entry_13mp</item>
+        <item>@string/pref_camera_picturesize_entry_8mp</item>
+        <item>@string/pref_camera_picturesize_entry_5mp</item>
+        <item>@string/pref_camera_picturesize_entry_5mp</item>
+        <item>@string/pref_camera_picturesize_entry_5mp</item>
+        <item>@string/pref_camera_picturesize_entry_4mp</item>
+        <item>@string/pref_camera_picturesize_entry_3mp</item>
+        <item>@string/pref_camera_picturesize_entry_2mp</item>
+        <item>@string/pref_camera_picturesize_entry_2mp_wide</item>
+        <item>@string/pref_camera_picturesize_entry_1_3mp</item>
+        <item>@string/pref_camera_picturesize_entry_1mp</item>
+        <item>@string/pref_camera_picturesize_entry_vga</item>
+        <item>@string/pref_camera_picturesize_entry_qvga</item>
+    </string-array>
+
+    <!-- When launching the camera app first time, we will set the picture
+         size to the first one in the list that is also supported by the
+         driver -->
+    <string-array name="pref_camera_picturesize_entryvalues" translatable="false">
+        <item>4128x3096</item>
+        <item>3264x2448</item>
+        <item>2592x1944</item>
+        <item>2592x1936</item>
+        <item>2560x1920</item>
+        <item>2688x1520</item>
+        <item>2048x1536</item>
+        <item>1600x1200</item>
+        <item>1920x1088</item>
+        <item>1280x960</item>
+        <item>1024x768</item>
+        <item>640x480</item>
+        <item>320x240</item>
+    </string-array>
+
+    <!-- Camera Preferences focus mode dialog box entries -->
+    <string-array name="pref_camera_focusmode_entries" translatable="false">
+        <item>@string/pref_camera_focusmode_entry_auto</item>
+        <item>@string/pref_camera_focusmode_entry_infinity</item>
+        <item>@string/pref_camera_focusmode_entry_macro</item>
+    </string-array>
+
+    <string-array name="pref_camera_focusmode_entryvalues" translatable="false">
+        <item>auto</item>
+        <item>infinity</item>
+        <item>macro</item>
+    </string-array>
+
+    <string-array name="pref_camera_focusmode_labels" translatable="false">
+        <item>@string/pref_camera_focusmode_label_auto</item>
+        <item>@string/pref_camera_focusmode_label_infinity</item>
+        <item>@string/pref_camera_focusmode_label_macro</item>
+    </string-array>
+
+    <!-- Camera Preferences flash mode dialog box entries -->
+    <string-array name="pref_camera_flashmode_entries" translatable="false">
+        <item>@string/pref_camera_flashmode_entry_off</item>
+        <item>@string/pref_camera_flashmode_entry_auto</item>
+        <item>@string/pref_camera_flashmode_entry_on</item>
+    </string-array>
+
+    <string-array name="pref_camera_flashmode_labels" translatable="false">
+        <item>@string/pref_camera_flashmode_label_off</item>
+        <item>@string/pref_camera_flashmode_label_auto</item>
+        <item>@string/pref_camera_flashmode_label_on</item>
+    </string-array>
+
+    <string-array name="pref_camera_flashmode_entryvalues" translatable="false">
+        <item>off</item>
+        <item>auto</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="camera_flashmode_icons" translatable="false">
+        <item>@drawable/ic_flash_off_holo_light</item>
+        <item>@drawable/ic_flash_auto_holo_light</item>
+        <item>@drawable/ic_flash_on_holo_light</item>
+    </array>
+
+    <array name="camera_flashmode_largeicons" translatable="false">
+        <item>@drawable/ic_flash_off_holo_light</item>
+        <item>@drawable/ic_flash_auto_holo_light</item>
+        <item>@drawable/ic_flash_on_holo_light</item>
+    </array>
+
+    <!-- Videocamera Preferences flash mode dialog box entries -->
+    <string-array name="pref_camera_video_flashmode_entries" translatable="false">
+        <item>@string/pref_camera_flashmode_entry_on</item>
+        <item>@string/pref_camera_flashmode_entry_off</item>
+    </string-array>
+
+    <string-array name="pref_camera_video_flashmode_labels" translatable="false">
+        <item>@string/pref_camera_flashmode_label_on</item>
+        <item>@string/pref_camera_flashmode_label_off</item>
+    </string-array>
+
+    <string-array name="pref_camera_video_flashmode_entryvalues" translatable="false">
+        <item>torch</item>
+        <item>off</item>
+    </string-array>
+
+    <array name="video_flashmode_icons" translatable="false">
+        <item>@drawable/ic_flash_on_holo_light</item>
+        <item>@drawable/ic_flash_off_holo_light</item>
+    </array>
+
+    <array name="video_flashmode_largeicons" translatable="false">
+        <item>@drawable/ic_flash_on_holo_light</item>
+        <item>@drawable/ic_flash_off_holo_light</item>
+    </array>
+
+    <string-array name="pref_camera_recordlocation_entryvalues" translatable="false">
+        <item>off</item>
+        <item>on</item>
+    </string-array>
+
+    <array name="pref_camera_recordlocation_entries" translatable="false">
+        <item>@string/setting_off</item>
+        <item>@string/setting_on</item>
+    </array>
+
+    <array name="pref_camera_recordlocation_labels" translatable="false">
+        <item>@string/pref_camera_location_label</item>
+        <item>@string/pref_camera_location_label</item>
+    </array>
+
+    <array name="camera_recordlocation_icons" translatable="false">
+        <item>@drawable/ic_location_off</item>
+        <item>@drawable/ic_location</item>
+    </array>
+
+    <array name="camera_recordlocation_largeicons" translatable="false">
+        <item>@drawable/ic_location_off</item>
+        <item>@drawable/ic_location</item>
+    </array>
+
+    <!-- Camera Preferences White Balance dialog box entries -->
+    <string-array name="pref_camera_whitebalance_entries" translatable="false">
+        <item>@string/pref_camera_whitebalance_entry_incandescent</item>
+        <item>@string/pref_camera_whitebalance_entry_fluorescent</item>
+        <item>@string/pref_camera_whitebalance_entry_auto</item>
+        <item>@string/pref_camera_whitebalance_entry_daylight</item>
+        <item>@string/pref_camera_whitebalance_entry_cloudy</item>
+    </string-array>
+
+    <string-array name="pref_camera_whitebalance_labels" translatable="false">
+        <item>@string/pref_camera_whitebalance_label_incandescent</item>
+        <item>@string/pref_camera_whitebalance_label_fluorescent</item>
+        <item>@string/pref_camera_whitebalance_label_auto</item>
+        <item>@string/pref_camera_whitebalance_label_daylight</item>
+        <item>@string/pref_camera_whitebalance_label_cloudy</item>
+    </string-array>
+
+    <string-array name="pref_camera_whitebalance_entryvalues" translatable="false">
+        <item>incandescent</item>
+        <item>fluorescent</item>
+        <item>auto</item>
+        <item>daylight</item>
+        <item>cloudy-daylight</item>
+    </string-array>
+
+    <array name="whitebalance_icons" translatable="false">
+        <item>@drawable/ic_wb_incandescent</item>
+        <item>@drawable/ic_wb_fluorescent</item>
+        <item>@drawable/ic_wb_auto</item>
+        <item>@drawable/ic_wb_sunlight</item>
+        <item>@drawable/ic_wb_cloudy</item>
+    </array>
+
+    <array name="whitebalance_largeicons" translatable="false">
+        <item>@drawable/ic_wb_incandescent</item>
+        <item>@drawable/ic_wb_fluorescent</item>
+        <item>@drawable/ic_wb_auto</item>
+        <item>@drawable/ic_wb_sunlight</item>
+        <item>@drawable/ic_wb_cloudy</item>
+    </array>
+
+    <array name="camera_wb_indicators" translatable="false">
+        <item>@drawable/ic_indicator_wb_tungsten</item>
+        <item>@drawable/ic_indicator_wb_fluorescent</item>
+        <item>@drawable/ic_indicator_wb_off</item>
+        <item>@drawable/ic_indicator_wb_daylight</item>
+        <item>@drawable/ic_indicator_wb_cloudy</item>
+    </array>
+
+    <!-- Camera Preferences Scene Mode dialog box entries -->
+    <string-array name="pref_camera_scenemode_entries" translatable="false">
+        <item>@string/pref_camera_scenemode_entry_action</item>
+        <item>@string/pref_camera_scenemode_entry_night</item>
+        <item>@string/pref_camera_scenemode_entry_auto</item>
+        <item>@string/pref_camera_scenemode_entry_sunset</item>
+        <item>@string/pref_camera_scenemode_entry_party</item>
+    </string-array>
+
+    <string-array name="pref_camera_scenemode_labels">
+        <item>@string/pref_camera_scenemode_label_action</item>
+        <item>@string/pref_camera_scenemode_label_night</item>
+        <item>@string/pref_camera_scenemode_label_auto</item>
+        <item>@string/pref_camera_scenemode_label_sunset</item>
+        <item>@string/pref_camera_scenemode_label_party</item>
+    </string-array>
+
+    <array name="pref_camera_scenemode_icons">
+        <item>@drawable/ic_sce_action</item>
+        <item>@drawable/ic_sce_night</item>
+        <item>@drawable/ic_sce_off</item>
+        <item>@drawable/ic_sce_sunset</item>
+        <item>@drawable/ic_sce_party</item>
+    </array>
+
+    <string-array name="pref_camera_scenemode_entryvalues" translatable="false">
+        <item>action</item>
+        <item>night</item>
+        <item>auto</item>
+        <item>sunset</item>
+        <item>party</item>
+    </string-array>
+
+    <array name="camera_id_entries" translatable="false">
+        <item>@string/pref_camera_id_entry_back</item>
+        <item>@string/pref_camera_id_entry_front</item>
+    </array>
+
+    <array name="camera_id_labels" translatable="false">
+        <item>@string/pref_camera_id_label_back</item>
+        <item>@string/pref_camera_id_label_front</item>
+    </array>
+
+    <array name="camera_id_icons" translatable="false">
+        <item>@drawable/ic_switch_back</item>
+        <item>@drawable/ic_switch_front</item>
+    </array>
+
+    <array name="camera_id_largeicons" translatable="false">
+        <item>@drawable/ic_switch_back</item>
+        <item>@drawable/ic_switch_front</item>
+    </array>
+
+    <string-array name="pref_video_effect_entries" translatable="false">
+        <item>@string/effect_none</item>
+        <item>@string/effect_goofy_face_squeeze</item>
+        <item>@string/effect_goofy_face_big_eyes</item>
+        <item>@string/effect_goofy_face_big_mouth</item>
+        <item>@string/effect_goofy_face_small_mouth</item>
+        <item>@string/effect_goofy_face_big_nose</item>
+        <item>@string/effect_goofy_face_small_eyes</item>
+        <item>@string/effect_backdropper_space</item>
+        <item>@string/effect_backdropper_sunset</item>
+        <item>@string/effect_backdropper_gallery</item>
+    </string-array>
+
+    <string-array name="pref_video_effect_entryvalues" translatable="false">
+        <item>@string/pref_video_effect_default</item>
+        <item>goofy_face/squeeze</item>
+        <item>goofy_face/big_eyes</item>
+        <item>goofy_face/big_mouth</item>
+        <item>goofy_face/small_mouth</item>
+        <item>goofy_face/big_nose</item>
+        <item>goofy_face/small_eyes</item>
+        <item>backdropper/file:///system/media/video/AndroidInSpace.480p.mp4</item>
+        <item>backdropper/file:///system/media/video/Sunset.480p.mp4</item>
+        <item>backdropper/gallery</item>
+    </string-array>
+
+    <array name="video_effect_icons" translatable="false">
+        <item>@drawable/ic_effects_holo_light</item>
+        <item>@drawable/ic_video_effects_faces_squeeze_holo_dark</item>
+        <item>@drawable/ic_video_effects_faces_big_eyes_holo_dark</item>
+        <item>@drawable/ic_video_effects_faces_big_mouth_holo_dark</item>
+        <item>@drawable/ic_video_effects_faces_small_mouth_holo_dark</item>
+        <item>@drawable/ic_video_effects_faces_big_nose_holo_dark</item>
+        <item>@drawable/ic_video_effects_faces_small_eyes_holo_dark</item>
+        <item>@drawable/ic_video_effects_background_intergalactic_holo</item>
+        <item>@drawable/ic_video_effects_background_fields_of_wheat_holo</item>
+        <item>@drawable/ic_video_effects_background_normal_holo_dark</item>
+    </array>
+
+    <string-array name="pref_camera_hdr_entries" translatable="false">
+        <item>@string/setting_off</item>
+        <item>@string/setting_on</item>
+    </string-array>
+
+    <string-array name="pref_camera_hdr_labels" translatable="false">
+        <item>@string/pref_camera_hdr_label</item>
+        <item>@string/pref_camera_hdr_label</item>
+    </string-array>
+
+    <string-array name="pref_camera_hdr_icons" translatable="false">
+        <item>@drawable/ic_hdr_off</item>
+        <item>@drawable/ic_hdr</item>
+    </string-array>
+
+    <string-array name="pref_camera_hdr_entryvalues" translatable="false">
+        <item>@string/setting_off_value</item>
+        <item>@string/setting_on_value</item>
+    </string-array>
+
+    <string-array name="pref_camera_timer_sound_entries" translatable="false">
+        <item>@string/setting_off</item>
+        <item>@string/setting_on</item>
+    </string-array>
+
+    <string-array name="pref_camera_timer_sound_entryvalues" translatable="false">
+        <item>@string/setting_off_value</item>
+        <item>@string/setting_on_value</item>
+    </string-array>
+
+    <!-- Default focus mode setting.-->
+    <string-array name="pref_camera_focusmode_default_array" translatable="false">
+        <item>continuous-picture</item>
+        <item>auto</item>
+    </string-array>
+
+    <!-- Icons for exposure compensation -->
+    <array name="pref_camera_exposure_icons" translatable="false">
+        <item>@drawable/ic_exposure_n3</item>
+        <item>@drawable/ic_exposure_n2</item>
+        <item>@drawable/ic_exposure_n1</item>
+        <item>@drawable/ic_exposure_0</item>
+        <item>@drawable/ic_exposure_p1</item>
+        <item>@drawable/ic_exposure_p2</item>
+        <item>@drawable/ic_exposure_p3</item>
+    </array>
+
+    <!--  Labels for Countdown timer -->
+    <string-array name="pref_camera_countdown_labels">
+        <item>@string/pref_camera_countdown_label_off</item>
+        <item>@string/pref_camera_countdown_label_one</item>
+        <item>@string/pref_camera_countdown_label_three</item>
+        <item>@string/pref_camera_countdown_label_ten</item>
+        <item>@string/pref_camera_countdown_label_fifteen</item>
+    </string-array>
+
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f8ebd05..5a00a69 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -18,7 +18,29 @@
         <attr name="listPreferredItemHeightSmall" format="dimension" />
         <attr name="switchStyle" format="reference" />
     </declare-styleable>
-    <declare-styleable name="CenteredLinearLayout">
-        <attr name="max_width" format="dimension" />
+
+    <!-- Camera resources below -->
+
+    <declare-styleable name="CameraPreference">
+        <attr name="title" format="string" />
     </declare-styleable>
+    <declare-styleable name="ListPreference">
+        <attr name="key" format="string" />
+        <attr name="defaultValue" format="string|reference" />
+        <attr name="entryValues" format="reference" />
+        <attr name="entries" format="reference" />
+        <attr name="labelList" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="IconIndicator">
+        <attr name="icons" format="reference" />
+        <attr name="modes" format="reference" />
+    </declare-styleable>
+    <declare-styleable name="IconListPreference">
+        <!-- If a preference does not have individual icons for each entry, it can has a single icon to represent it. -->
+        <attr name="singleIcon" format="reference" />
+        <attr name="icons" />
+        <attr name="largeIcons" format="reference" />
+        <attr name="images" format="reference" />
+    </declare-styleable>
+
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 9fd7802..c1bb52b 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -40,4 +40,33 @@
     <color name="slideshow_background">#1A1A1A</color>
 
     <color name="button_dark_transparent_background">#6000</color>
+
+    <color name="ingest_highlight_semitransparent">#8833b5e5</color>
+    <color name="ingest_date_tile_text">#33b5e5</color>
+
+    <!-- Camera resources below -->
+
+    <color name="recording_time_elapsed_text">#FFFFFFFF</color>
+    <color name="recording_time_remaining_text">#FFFF0033</color>
+    <color name="on_viewfinder_label_background_color">#77333333</color>
+    <color name="review_control_pressed_color">#FF33B5E5</color>
+    <color name="review_control_pressed_fan_color">#3F33B5E5</color>
+    <color name="review_background">#FF000000</color>
+    <color name="icon_disabled_color">#DD777777</color>
+    <color name="time_lapse_arc">#FFC5C5C5</color>
+    <color name="indicator_background">#40000000</color>
+    <color name="popup_title_color">#ff33b5e5</color>
+    <color name="popup_background">#ff282828</color>
+    <color name="pano_progress_empty">#FF2E2E2E</color>
+    <color name="pano_progress_done">#FF33525E</color>
+    <color name="pano_progress_indication">#FF0099CC</color>
+    <color name="pano_progress_indication_fast">#FFFF2222</color>
+    <color name="mode_selection_border">#33B5E5</color>
+    <color name="holo_blue_light">#ff33b5e5</color>
+    <color name="bright_foreground_disabled_holo_dark">#ff4c4c4c</color>
+    <color name="bright_foreground_holo_dark">#fff3f3f3</color>
+    <color name="face_detect_start">#80ffffff</color>
+    <color name="face_detect_success">#8050d060</color>
+    <color name="face_detect_fail">#80d05060</color>
+
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..33e1e14
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2012 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.
+-->
+
+<!-- Camera app resources that may need to be customized
+     for different hardware or product builds. -->
+<resources>
+    <!-- Maximum recording length in milliseconds. 0 means unlimited. -->
+    <integer name="max_video_recording_length">0</integer>
+</resources>
diff --git a/res/values/crop_colors.xml b/res/values/crop_colors.xml
new file mode 100644
index 0000000..3f64c50
--- /dev/null
+++ b/res/values/crop_colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <color name="crop_shadow_color">#CF000000</color>
+    <color name="crop_shadow_wp_color">#4F000000</color>
+    <color name="crop_wp_markers">#7FFFFFFF</color>
+</resources>
diff --git a/res/values/crop_dimens.xml b/res/values/crop_dimens.xml
new file mode 100644
index 0000000..fc91dbf
--- /dev/null
+++ b/res/values/crop_dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <dimen name="preview_margin">2dp</dimen>
+    <dimen name="shadow_margin">5dp</dimen>
+    <dimen name="crop_min_side">45dp</dimen>
+    <dimen name="crop_touch_tolerance">20dp</dimen>
+    <dimen name="wp_selector_dash_length">4dp</dimen>
+    <dimen name="wp_selector_off_length">4dp</dimen>
+</resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
new file mode 100644
index 0000000..7cc4be4
--- /dev/null
+++ b/res/values/dimens.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2009, The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <dimen name="hint_y_offset">64dp</dimen>
+    <dimen name="pano_mosaic_surface_height">240dp</dimen>
+    <dimen name="pano_review_button_width">70dp</dimen>
+    <dimen name="pano_review_button_height">45dp</dimen>
+    <dimen name="setting_popup_right_margin">5dp</dimen>
+    <dimen name="setting_row_height">50dp</dimen>
+    <dimen name="setting_item_text_size">18sp</dimen>
+    <dimen name="setting_knob_width">20dp</dimen>
+    <dimen name="setting_knob_text_size">20dp</dimen>
+    <dimen name="setting_item_text_width">95dp</dimen>
+    <dimen name="setting_popup_window_width">240dp</dimen>
+    <dimen name="setting_item_list_margin">14dp</dimen>
+    <dimen name="indicator_bar_width">48dp</dimen>
+    <dimen name="popup_title_text_size">22dp</dimen>
+    <dimen name="popup_title_frame_min_height">49dp</dimen>
+    <dimen name="big_setting_popup_window_width">320dp</dimen>
+    <dimen name="setting_item_icon_width">28dp</dimen>
+    <dimen name="effect_setting_item_icon_width">40dp</dimen>
+    <dimen name="effect_setting_item_text_size">12sp</dimen>
+    <dimen name="effect_setting_type_text_size">12sp</dimen>
+    <dimen name="effect_setting_type_text_min_height">36dp</dimen>
+    <dimen name="effect_setting_clear_text_size">20dp</dimen>
+    <dimen name="effect_setting_clear_text_min_height">45dp</dimen>
+    <dimen name="effect_setting_type_text_left_padding">16dp</dimen>
+    <dimen name="onscreen_indicators_height">28dp</dimen>
+    <dimen name="onscreen_exposure_indicator_text_size">15dp</dimen>
+    <dimen name="switch_padding">16dp</dimen>
+    <dimen name="switch_min_width">96dp</dimen>
+    <dimen name="switch_text_max_width">44dp</dimen>
+    <dimen name="thumb_text_padding">12dp</dimen>
+    <dimen name="thumb_text_size">14sp</dimen>
+    <dimen name="setting_popup_right_margin_large">8dp</dimen>
+    <dimen name="setting_row_height_large">54dp</dimen>
+    <dimen name="setting_popup_window_width_large">260dp</dimen>
+    <dimen name="indicator_bar_width_large">72dp</dimen>
+    <dimen name="setting_item_icon_width_large">48dp</dimen>
+    <dimen name="onscreen_indicators_height_large">36dp</dimen>
+    <dimen name="pano_mosaic_surface_height_xlarge">480dp</dimen>
+    <dimen name="pano_review_button_width_xlarge">180dp</dimen>
+    <dimen name="pano_review_button_height_xlarge">115dp</dimen>
+    <dimen name="setting_row_height_xlarge">50dp</dimen>
+    <dimen name="setting_item_text_size_xlarge">21dp</dimen>
+    <dimen name="setting_knob_width_xlarge">50dp</dimen>
+    <dimen name="setting_item_text_width_xlarge">130dp</dimen>
+    <dimen name="setting_popup_window_width_xlarge">410dp</dimen>
+    <dimen name="setting_item_list_margin_xlarge">24dp</dimen>
+    <dimen name="indicator_bar_width_xlarge">13dp</dimen>
+    <dimen name="popup_title_text_size_xlarge">22dp</dimen>
+    <dimen name="popup_title_frame_min_height_xlarge">60dp</dimen>
+    <dimen name="big_setting_popup_window_width_xlarge">590dp</dimen>
+    <dimen name="setting_item_icon_width_xlarge">35dp</dimen>
+    <dimen name="effect_setting_item_icon_width_xlarge">54dp</dimen>
+    <dimen name="effect_setting_item_text_size_xlarge">21dp</dimen>
+    <dimen name="effect_setting_type_text_size_xlarge">21dp</dimen>
+    <dimen name="effect_setting_type_text_min_height_xlarge">34dp</dimen>
+    <dimen name="effect_setting_clear_text_size_xlarge">23dp</dimen>
+    <dimen name="effect_setting_clear_text_min_height_xlarge">44dp</dimen>
+    <dimen name="effect_setting_type_text_left_padding_xlarge">26dp</dimen>
+    <dimen name="onscreen_indicators_height_xlarge">36dp</dimen>
+    <dimen name="onscreen_exposure_indicator_text_size_xlarge">18dp</dimen>
+    <dimen name="pie_radius_start">80dp</dimen>
+    <dimen name="pie_radius_increment">48dp</dimen>
+    <dimen name="pie_touch_slop">12dp</dimen>
+    <dimen name="pie_touch_offset">32dp</dimen>
+    <dimen name="pie_view_size">48dp</dimen>
+    <dimen name="pie_arc_offset">48dp</dimen>
+    <dimen name="pie_item_radius">370dp</dimen>
+    <dimen name="pie_arc_radius">214dp</dimen>
+    <dimen name="pie_deadzone_width">36dp</dimen>
+    <dimen name="pie_anglezone_width">92dp</dimen>
+    <dimen name="focus_radius_offset">8dp</dimen>
+    <dimen name="focus_inner_offset">24dp</dimen>
+    <dimen name="focus_outer_stroke">3dp</dimen>
+    <dimen name="focus_inner_stroke">2dp</dimen>
+    <dimen name="zoom_ring_min">48dp</dimen>
+    <dimen name="switcher_size">72dp</dimen>
+    <dimen name="face_circle_stroke">2dip</dimen>
+    <dimen name="zoom_font_size">14pt</dimen>
+    <dimen name="shutter_offset">-22dp</dimen>
+    <dimen name="size_thumbnail">200dip</dimen>
+    <dimen name="size_preview">600dip</dimen>
+    <dimen name="navigation_bar_height">48dip</dimen>
+    <dimen name="navigation_bar_width">42dip</dimen>
+    <dimen name="capture_size">48dip</dimen>
+    <dimen name="capture_border">8dip</dimen>
+    <dimen name="capture_margin_right">16dip</dimen>
+    <dimen name="capture_margin_top">16dip</dimen>
+    <dimen name="camera_controls_size">0dip</dimen>
+</resources>
diff --git a/res/values/dimensions.xml b/res/values/dimensions.xml
index dc9e8c3..94697d1 100644
--- a/res/values/dimensions.xml
+++ b/res/values/dimensions.xml
@@ -19,7 +19,7 @@
     <dimen name="stack_photo_width">160dp</dimen>
     <dimen name="stack_photo_height">120dp</dimen>
 
-    <!-- configuration for album set page -->
+    <!-- configuration for legacy album set page -->
     <integer name="albumset_rows_land">2</integer>
     <integer name="albumset_rows_port">3</integer>
     <dimen name="albumset_padding_top">7dp</dimen>
@@ -50,4 +50,9 @@
     <!--  configuration for filtershow UI -->
     <dimen name="thumbnail_size">96dip</dimen>
     <dimen name="thumbnail_margin">3dip</dimen>
+    <dimen name="action_item_height">175dip</dimen>
+
+    <!-- configuration for album set page -->
+    <dimen name="album_set_item_image_height">120dp</dimen>
+    <dimen name="album_set_item_width">140dp</dimen>
 </resources>
diff --git a/res/values/filtershow_color.xml b/res/values/filtershow_color.xml
index 15bf2bc..927bfa2 100644
--- a/res/values/filtershow_color.xml
+++ b/res/values/filtershow_color.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
+<!-- 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.
@@ -13,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+
 <resources>
     <color name="yellow">#FFFF00</color>
     <color name="green">#00FF00</color>
@@ -23,4 +24,18 @@
     <color name="background_toolbar">#363949</color>
     <color name="background_main_toolbar">#232323</color>
     <color name="toolbar_separation_line">#333333</color>
+    <color name="slider_dot_color">#6464FF</color>
+    <color name="slider_line_color">#33B5E5</color>
+    <color name="state_panel_separation_line">#232323</color>
+    <color name="filtershow_background">#333333</color>
+    <color name="filtershow_graphic">#717171</color>
+    <color name="filtershow_stateview_end_background">#232323</color>
+    <color name="filtershow_stateview_end_text">#a7a7a7</color>
+    <color name="filtershow_stateview_background">#464646</color>
+    <color name="filtershow_stateview_text">#FFFFFF</color>
+    <color name="filtershow_stateview_selected_background">#c8c8c8</color>
+    <color name="filtershow_stateview_selected_text">#000000</color>
+    <color name="filtershow_categoryview_background">#1a1a1a</color>
+    <color name="filtershow_categoryview_text">#a7a7a7</color>
+    <color name="filtershow_category_selection">#ffffffff</color>
 </resources>
\ No newline at end of file
diff --git a/res/values/filtershow_ids.xml b/res/values/filtershow_ids.xml
new file mode 100644
index 0000000..8ac2941
--- /dev/null
+++ b/res/values/filtershow_ids.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* 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.
+*/
+-->
+<resources>
+    <!-- Buttons ids for the filters -->
+    <item type="id" name="tinyplanetButton" />
+    <item type="id" name="vignetteButton" />
+    <item type="id" name="vibranceButton" />
+    <item type="id" name="contrastButton" />
+    <item type="id" name="saturationButton" />
+    <item type="id" name="bwfilterButton" />
+    <item type="id" name="wbalanceButton" />
+    <item type="id" name="hueButton" />
+    <item type="id" name="exposureButton" />
+    <item type="id" name="shadowRecoveryButton" />
+    <item type="id" name="highlightRecoveryButton" />
+    <item type="id" name="sharpenButton" />
+    <item type="id" name="curvesButtonRGB" />
+    <item type="id" name="negativeButton" />
+    <item type="id" name="edgeButton" />
+    <item type="id" name="kmeansButton" />
+    <item type="id" name="downsampleButton" />
+    <item type="id" name="drawOnImageButton" />
+    <item type="id" name="imageCurves" />
+    <item type="id" name="imageZoom" />
+    <item type="id" name="editorDraw" />
+    <item type="id" name="editorRedEye" />
+    <item type="id" name="imageOnlyEditor" />
+    <item type="id" name="vignetteEditor" />
+    <item type="id" name="editorCrop" />
+    <item type="id" name="editorFlip" />
+    <item type="id" name="editorRotate" />
+    <item type="id" name="editorStraighten" />
+    <item type="id" name="editorParametric" />
+</resources>
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
index 1f00e8a..c8ad2a9 100644
--- a/res/values/filtershow_strings.xml
+++ b/res/values/filtershow_strings.xml
@@ -15,7 +15,6 @@
 -->
 
 <resources>
-
     <!--  Title for the image editor activity [CHAR LIMIT=NONE]-->
     <string name="title_activity_filter_show">Photo Editor</string>
 
@@ -23,9 +22,14 @@
     <string name="cannot_load_image">Cannot load the image!</string>
     <!--  String displayed when showing the original image [CHAR LIMIT=NONE] -->
     <string name="original_picture_text">@string/original</string>
+    <!--  String displayed when setting the homepage wallpaper in the background [CHAR LIMIT=NONE] -->
+    <string name="setting_wallpaper">Setting wallpaper</string>
 
     <!--  generic strings -->
 
+
+    <!--  Text for to display on a download failure [CHAR LIMIT=NONE] -->
+    <string name="download_failure">Could not download photo. Network unavailable.</string>
     <!--  Text for original image [CHAR LIMIT=20] -->
     <string name="original">Original</string>
     <!--  Text for filters that apply a border to a picture [CHAR LIMIT=20] -->
@@ -33,23 +37,28 @@
 
     <!--  actionbar menu -->
 
-    <!--  Text for the actionbar confirmation button [CHAR LIMIT=20] -->
-    <string name="done">Done</string>
     <!--  Text for the undo menu item [CHAR LIMIT=20] -->
     <string name="filtershow_undo">Undo</string>
     <!--  Text for redo menu item [CHAR LIMIT=20] -->
     <string name="filtershow_redo">Redo</string>
-    <!--  Text for the history panel menu item [CHAR LIMIT=20] -->
-    <string name="show_history_panel">Show History</string>
-    <!--  Text for the history panel menu item [CHAR LIMIT=20] -->
-    <string name="hide_history_panel">Hide History</string>
-    <!--  Text for the image state panel menu item [CHAR LIMIT=20] -->
-    <string name="show_imagestate_panel">Show Image State</string>
-    <!--  Text for the image state panel menu item [CHAR LIMIT=20] -->
-    <string name="hide_imagestate_panel">Hide Image State</string>
+    <!--  Text for the image menu item showing the filters that have been applied [CHAR LIMIT=30] -->
+    <string name="show_imagestate_panel">Show Applied Effects</string>
+    <!--  Text for the image state panel menu item [CHAR LIMIT=30] -->
+    <string name="hide_imagestate_panel">Hide Applied Effects</string>
     <!--  Name for the overflow menu item for settings [CHAR LIMIT=20] -->
     <string name="menu_settings">Settings</string>
 
+    <!--  Exit Dialog -->
+
+    <!--  String displayed when exiting with unsaved changes [CHAR LIMIT=NONE] -->
+    <string name="unsaved">There are unsaved changes to this image.</string>
+    <!--  String displayed when exiting with unsaved changes [CHAR LIMIT=NONE] -->
+    <string name="save_before_exit">Do you want to save before exiting?</string>
+    <!--  String displayed when saving and exiting editor [CHAR LIMIT=NONE] -->
+    <string name="save_and_exit">Save and Exit</string>
+    <!--  String displayed when exiting editor[CHAR LIMIT=NONE] -->
+    <string name="exit">Exit</string>
+
     <!--  History Panel -->
 
     <!--  Text for the history panel title [CHAR LIMIT=50] -->
@@ -62,7 +71,7 @@
     <!--  Image state panel -->
 
     <!--  Text for the image state panel title [CHAR LIMIT=50] -->
-    <string name="imageState">Current Image State</string>
+    <string name="imageState">Applied Effects</string>
 
     <!--  Additional filters buttons  -->
 
@@ -92,55 +101,100 @@
     <string name="aspectNone_effect">None</string>
     <!--  Label for the aspect None effect [CHAR LIMIT=15] -->
     <string name="aspectOriginal_effect">@string/original</string>
+    <!-- Label for when the aspect ratio is fixed to a value [CHAR LIMIT=15] -->
+    <string name="Fixed">Fixed</string>
     <!--  Label for the tuny planet effect [CHAR LIMIT=10] -->
     <string name="tinyplanet">Tiny Planet</string>
 
     <!--  Filters buttons -->
 
-    <!--  Label for the exposure filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image exposure (brightness) filter button [CHAR LIMIT=10] -->
     <string name="exposure" msgid="1229093066434614811">Exposure</string>
-    <!--  Label for the sharpen filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image sharpness (clarity, distinctness) filter button [CHAR LIMIT=10] -->
     <string name="sharpness">Sharpness</string>
-    <!--  Label for the contrast filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image contrast (color difference) filter button [CHAR LIMIT=10] -->
     <string name="contrast">Contrast</string>
-    <!--  Label for the vibrance filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image vibrance (strengthens colors) filter button [CHAR LIMIT=10] -->
     <string name="vibrance">Vibrance</string>
-    <!--  Label for the saturation filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image saturation (brightens colors) filter button [CHAR LIMIT=10] -->
     <string name="saturation">Saturation</string>
-    <!--  Label for the BW filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image BW filter (makes black & white) button [CHAR LIMIT=10] -->
     <string name="bwfilter">BW Filter</string>
-    <!--  Label for the White Balance filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image Autocolor filter (makes off-white colors whiter) button [CHAR LIMIT=10] -->
     <string name="wbalance">Autocolor</string>
-    <!--  Label for the Hue filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image Hue filter (color, shade, tinge, tone) button [CHAR LIMIT=10] -->
     <string name="hue">Hue</string>
-    <!--  Label for the shadow recovery filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image shadow recovery (lightens/darkens shadows) filter button [CHAR LIMIT=10] -->
     <string name="shadow_recovery">Shadows</string>
-    <!--  Label for the curves filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image highlights recovery (lightens/darkens bright regions) filter button [CHAR LIMIT=15] -->
+    <string name="highlight_recovery">Highlights</string>
+    <!--  Label for the image curves filter button [CHAR LIMIT=10] -->
     <string name="curvesRGB">Curves</string>
-    <!--  Label for the vignette filter button [CHAR LIMIT=15] -->
+    <!--  Label for the image vignette filter (darkens photo around edges) button [CHAR LIMIT=10] -->
     <string name="vignette">Vignette</string>
-    <!--  Label for the photo effect that removes redeye. [CHAR LIMIT=15] -->
+    <!--  Label for the image effect that removes redeye. [CHAR LIMIT=10] -->
     <string name="redeye">Red Eye</string>
-    <!--  Label for the straighten effect [CHAR LIMIT=15] -->
+    <!--  Label for the that allows drawing on Image [CHAR LIMIT=10] -->
+    <string name="imageDraw">Draw</string>
+    <!--  Label for the image straighten effect [CHAR LIMIT=15] -->
     <string name="straighten" msgid="5217801513491493491">Straighten</string>
-    <!--  Label for the crop effect [CHAR LIMIT=15] -->
+    <!--  Label for the image crop effect [CHAR LIMIT=15] -->
     <string name="crop" msgid="5584000454518174632">Crop</string>
-    <!--  Label for the rotate effect [CHAR LIMIT=15] -->
+    <!--  Label for the image rotate effect [CHAR LIMIT=15] -->
     <string name="rotate" msgid="460017689320955494">Rotate</string>
-    <!--  Label for the flip effect [CHAR LIMIT=15] -->
+    <!--  Label for the image flip effect [CHAR LIMIT=15] -->
     <string name="mirror">Mirror</string>
-    <!--  Label for the original [CHAR LIMIT=15] -->
+    <!-- Name for the photo effect that inverts photo to negative images. [CHAR LIMIT=10] -->
+    <string name="negative">Negative</string>
+    <!--  Label for having no filters applied to the image [CHAR LIMIT=10] -->
     <string name="none" msgid="3601545724573307541">None</string>
+    <!--  Label for the image edges effect (highlights edges in image) [CHAR LIMIT=10] -->
+    <string name="edge">Edges</string>
+    <!--  Label for an image effect that replicates the "pop art" style of segmenting
+          images into solid colors, as popularized by Andy Warhol [CHAR LIMIT=10] -->
+    <string name="kmeans">Warhol</string>
+    <!--  Label for the image downsampling effect (makes image smaller) [CHAR LIMIT=15] -->
+    <string name="downsample">Downsample</string>
 
     <!--  Labels for the curves tool -->
 
     <!--  Label for the curves tool, all channels (RGB) [CHAR LIMIT=3] -->
     <string name="curves_channel_rgb">RGB</string>
-    <!--  Label for the curves tool, Red color channel [CHAR LIMIT=10] -->
+    <!--  Label for the curves tool, Red color channel [CHAR LIMIT=14] -->
     <string name="curves_channel_red">Red</string>
-    <!--  Label for the curves tool, Green color channel [CHAR LIMIT=10] -->
+    <!--  Label for the curves tool, Green color channel [CHAR LIMIT=14] -->
     <string name="curves_channel_green">Green</string>
-    <!--  Label for the curves tool, Blue color channel [CHAR LIMIT=10] -->
+    <!--  Label for the curves tool, Blue color channel [CHAR LIMIT=14] -->
     <string name="curves_channel_blue">Blue</string>
 
+    <!--  Label for the The style to draw in [CHAR LIMIT=14] -->
+    <string name="draw_style">Style</string>
+    <!--  Label for the size to draw in in [CHAR LIMIT=14] -->
+    <string name="draw_size">Size</string>
+    <!--  Label for the color to draw in [CHAR LIMIT=14] -->
+    <string name="draw_color">Color</string>
+    <!--  Label for the line style of drawing in [CHAR LIMIT=14] -->
+    <string name="draw_style_line">Lines</string>
+    <!--  Label for the Marker brush style of drawing in [CHAR LIMIT=14] -->
+    <string name="draw_style_brush_spatter">Marker</string>
+    <!--  Label for the Spatter brush style of drawing in [CHAR LIMIT=14] -->
+    <string name="draw_style_brush_marker">Spatter</string>
+    <!--  Label for the removing drawing from screen [CHAR LIMIT=14] -->
+    <string name="draw_clear">Clear</string>
+
+    <!--  Label for the select the color [CHAR LIMIT=35] -->
+    <string name="color_pick_select">Choose custom color</string>
+    <!--  The title for the color pick dialog [CHAR LIMIT=20] -->
+    <string name="color_pick_title">Select Color</string>
+    <!--  The title for draw size [CHAR LIMIT=50] -->
+    <string name="draw_size_title">Select Size</string>
+    <!--  The accept the draw size [CHAR LIMIT=20] -->
+    <string name="draw_size_accept">OK</string>
+
+    <!--  Name used to indicate the original image in the state panel [CHAR LIMIT=20] -->
+    <string name="state_panel_original">Original</string>
+
+    <!--  Name used to indicate the final image in the state panel [CHAR LIMIT=20] -->
+    <string name="state_panel_result">Result</string>
+
 </resources>
diff --git a/res/values/filtershow_styles.xml b/res/values/filtershow_styles.xml
index 4600eeb..4162ccd 100644
--- a/res/values/filtershow_styles.xml
+++ b/res/values/filtershow_styles.xml
@@ -52,4 +52,17 @@
         <item name="android:scaleType">centerInside</item>
     </style>
 
+    <style name="FilterShowSlider">
+        <item name="android:indeterminateOnly">false</item>
+        <item name="android:progressDrawable">@drawable/filtershow_slider</item>
+        <item name="android:indeterminateDrawable">@drawable/filtershow_slider</item>
+        <item name="android:minHeight">13dip</item>
+        <item name="android:maxHeight">13dip</item>
+        <item name="android:thumb">@drawable/filtershow_scrubber</item>
+        <item name="android:thumbOffset">16dip</item>
+        <item name="android:focusable">true</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+    </style>
+
 </resources>
\ No newline at end of file
diff --git a/res/values/filtershow_values.xml b/res/values/filtershow_values.xml
new file mode 100644
index 0000000..f516a39
--- /dev/null
+++ b/res/values/filtershow_values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <!-- Specify the screen orientation -->
+    <bool name="only_use_portrait">true</bool>
+</resources>
\ No newline at end of file
diff --git a/res/values/filtershow_values_attrs.xml b/res/values/filtershow_values_attrs.xml
index 67c645d..32a3a87 100644
--- a/res/values/filtershow_values_attrs.xml
+++ b/res/values/filtershow_values_attrs.xml
@@ -19,4 +19,14 @@
         <attr name="android:text"/>
         <attr name="android:textColor"/>
     </declare-styleable>
+    <declare-styleable name="CenteredLinearLayout">
+        <attr name="max_width" format="dimension" />
+    </declare-styleable>
+    <declare-styleable name="StatePanelTrack">
+        <attr name="elemSize" format="dimension" />
+        <attr name="elemEndSize" format="dimension" />
+    </declare-styleable>
+    <declare-styleable name="CategoryTrack">
+        <attr name="iconSize" format="dimension" />
+    </declare-styleable>
 </resources>
\ No newline at end of file
diff --git a/res/values/iconbutton_styles.xml b/res/values/iconbutton_styles.xml
new file mode 100644
index 0000000..e33460a
--- /dev/null
+++ b/res/values/iconbutton_styles.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2012 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.
+-->
+<resources>
+    <style name="IconButton">
+        <item name="android:layout_width">96dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:background">@drawable/filtershow_button_background</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textSize">14dp</item>
+        <item name="android:scaleType">centerInside</item>
+        <item name="android:gravity">center</item>
+        <item name="android:padding">6dp</item>
+        <item name="android:drawablePadding">6dp</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:marqueeRepeatLimit">marquee_forever</item>
+        <item name="android:singleLine">true</item>
+    </style>
+
+    <style name="FilterIconButton">
+        <item name="android:layout_width">70dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:background">@drawable/filtershow_button_background</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textSize">13dp</item>
+        <item name="android:scaleType">centerInside</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingLeft">3dp</item>
+        <item name="android:paddingRight">3dp</item>
+        <item name="android:paddingTop">6dp</item>
+        <item name="android:paddingBottom">6dp</item>
+        <item name="android:ellipsize">marquee</item>
+        <item name="android:marqueeRepeatLimit">marquee_forever</item>
+        <item name="android:singleLine">true</item>
+    </style>
+</resources>
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 7babba0..fefd5f0 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -19,4 +19,5 @@
 <resources>
     <item type="id" name="action_toggle_full_caching" />
     <item type="id" name="action_select_all" />
+    <item type="id" name="viewpager" />
 </resources>
diff --git a/res/values/photoeditor_arrays.xml b/res/values/photoeditor_arrays.xml
deleted file mode 100644
index 4c27215..0000000
--- a/res/values/photoeditor_arrays.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<resources>
-    <array name="color_picker_colors">
-        <item>@color/color_picker_preset_color1</item>
-        <item>@color/color_picker_preset_color2</item>
-        <item>@color/color_picker_preset_color3</item>
-        <item>@color/color_picker_preset_color4</item>
-        <item>@color/color_picker_preset_color5</item>
-        <item>@color/color_picker_preset_color6</item>
-        <item>@color/color_picker_preset_color7</item>
-        <item>@color/color_picker_preset_color8</item>
-        <item>@color/color_picker_preset_color9</item>
-        <item>@color/color_picker_preset_color10</item>
-        <item>@color/color_picker_preset_color11</item>
-        <item>@color/color_picker_preset_color12</item>
-        <item>@color/color_picker_preset_color13</item>
-        <item>@color/color_picker_preset_color14</item>
-        <item>@color/color_picker_preset_color15</item>
-        <item>@color/color_picker_preset_color16</item>
-        <item>@color/color_picker_preset_color17</item>
-        <item>@color/color_picker_preset_color18</item>
-        <item>@color/color_picker_preset_color19</item>
-        <item>@color/color_picker_preset_color20</item>
-        <item>@color/color_picker_preset_color21</item>
-        <item>@color/color_picker_preset_color22</item>
-        <item>@color/color_picker_preset_color23</item>
-        <item>@color/color_picker_preset_color24</item>
-    </array>
-</resources>
diff --git a/res/values/photoeditor_colors.xml b/res/values/photoeditor_colors.xml
deleted file mode 100644
index dfcc913..0000000
--- a/res/values/photoeditor_colors.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<resources>
-    <color name="translucent_black">#67000000</color>
-    <color name="translucent_white">#99CCCCCC</color>
-    <color name="translucent_cyan">#99008AFF</color>
-    <color name="opaque_cyan">#FF008AFF</color>
-    <color name="color_picker_preset_color1">#ddff1e00</color>
-    <color name="color_picker_preset_color2">#ddff5b00</color>
-    <color name="color_picker_preset_color3">#ddff9900</color>
-    <color name="color_picker_preset_color4">#ddffd600</color>
-    <color name="color_picker_preset_color5">#ddeaff00</color>
-    <color name="color_picker_preset_color6">#ddadff00</color>
-    <color name="color_picker_preset_color7">#dd70ff00</color>
-    <color name="color_picker_preset_color8">#dd32ff00</color>
-    <color name="color_picker_preset_color9">#dd00ff0a</color>
-    <color name="color_picker_preset_color10">#dd00ff47</color>
-    <color name="color_picker_preset_color11">#dd00ff84</color>
-    <color name="color_picker_preset_color12">#dd00ffc1</color>
-    <color name="color_picker_preset_color13">#dd00ffff</color>
-    <color name="color_picker_preset_color14">#dd00c1ff</color>
-    <color name="color_picker_preset_color15">#dd0084ff</color>
-    <color name="color_picker_preset_color16">#dd0047ff</color>
-    <color name="color_picker_preset_color17">#dd000aff</color>
-    <color name="color_picker_preset_color18">#dd3300ff</color>
-    <color name="color_picker_preset_color19">#dd7000ff</color>
-    <color name="color_picker_preset_color20">#ddad00ff</color>
-    <color name="color_picker_preset_color21">#ddea00ff</color>
-    <color name="color_picker_preset_color22">#ddff00d6</color>
-    <color name="color_picker_preset_color23">#ddff0098</color>
-    <color name="color_picker_preset_color24">#ddff005b</color>
-</resources>
diff --git a/res/values/photoeditor_strings.xml b/res/values/photoeditor_strings.xml
deleted file mode 100644
index 573b31a..0000000
--- a/res/values/photoeditor_strings.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Name for the photo editing activity to edit the selected photo. [CHAR LIMIT=NONE] -->
-    <string name="photoeditor_name">Photo Studio</string>
-
-    <!-- Toast shown when selected photo could not be loaded. [CHAR LIMIT=40] -->
-    <string name="loading_failure">Couldn\'t load the photo</string>
-
-    <!-- Toast shown when edited photo could not be saved. [CHAR LIMIT=48] -->
-    <string name="saving_failure">Couldn\'t save edited photo</string>
-
-    <!-- Toast shown when edited photo is successfully saved under %s folder [CHAR LIMIT=40] -->
-    <string name="photo_saved">Edited photo saved to <xliff:g id="folder_name">%s</xliff:g></string>
-
-    <!-- Dialog message prompted to confirm the user is abandoning unsaved changes. [CHAR LIMIT=40] -->
-    <string name="discard_unsaved_photo">Discard unsaved changes?</string>
-
-    <!-- Dialog yes button for the user to accept text presented in a dialog. [CHAR LIMIT=12] -->
-    <string name="yes">Yes</string>
-
-    <!-- Text button in the action bar for the user to save edited photo. [CHAR LIMIT=8] -->
-    <string name="save">SAVE</string>
-
-    <!-- Name for the photo effect that auto fixes exposure. [CHAR LIMIT=15] -->
-    <string name="autofix">Auto-fix</string>
-
-    <!-- Name for the photo effect that crops photo. [CHAR LIMIT=15] -->
-    <string name="crop">Crop</string>
-
-    <!-- Name for the photo effect that applies cross-process effect. [CHAR LIMIT=15] -->
-    <string name="crossprocess">Cross-process</string>
-
-    <!-- Name for the photo effect that applies black/white documentary styles. [CHAR LIMIT=15] -->
-    <string name="documentary">Documentary</string>
-
-    <!-- Name for the photo effect that doodles on photo. [CHAR LIMIT=15] -->
-    <string name="doodle">Doodle</string>
-
-    <!-- Name for the photo effect that makes photo only two color tones. [CHAR LIMIT=15] -->
-    <string name="duotone">Duo-tone</string>
-
-    <!-- Name for the photo effect that smooths facial skins. [CHAR LIMIT=15] -->
-    <string name="facelift">Face Glow</string>
-
-    <!-- Name for the photo effect that tans facial skins. [CHAR LIMIT=15] -->
-    <string name="facetan">Face Tan</string>
-
-    <!-- Name for the photo effect that fills back-light. [CHAR LIMIT=15] -->
-    <string name="filllight">Fill Light</string>
-
-    <!-- Name for the photo effect that applies fisheye lens distortion. [CHAR LIMIT=15] -->
-    <string name="fisheye">Fisheye</string>
-
-    <!-- Name for the photo effect that flips photo vertically or horizontally. [CHAR LIMIT=15] -->
-    <string name="flip">Flip</string>
-
-    <!-- Name for the photo effect that adds grains and noises. [CHAR LIMIT=15] -->
-    <string name="grain">Film Grain</string>
-
-    <!-- Name for the photo effect that converts colors to black-and-white, abbreviated B&W. [CHAR LIMIT=15] -->
-    <string name="grayscale">B&amp;W</string>
-
-    <!-- Name for the photo effect that converts colors to black-and-white in accessibility mode [CHAR LIMIT=30] -->
-    <string name="accessibility_black_and_white">Black and white</string>
-
-    <!-- Name for the photo effect that adds highlights. [CHAR LIMIT=15] -->
-    <string name="highlight">Highlights</string>
-
-    <!-- Name for the photo effect that applies lomo-camera styles. [CHAR LIMIT=15] -->
-    <string name="lomoish">Lomo</string>
-
-    <!-- Name for the photo effect that inverts photo to negative images. [CHAR LIMIT=15] -->
-    <string name="negative">Negative</string>
-
-    <!-- Name for the photo effect that applies posterization. [CHAR LIMIT=15] -->
-    <string name="posterize">Posterize</string>
-
-    <!-- Name for the photo effect that remove red eyes. [CHAR LIMIT=15] -->
-    <string name="redeye">Red Eye</string>
-
-    <!-- Name for the photo effect that rotates photo. [CHAR LIMIT=15] -->
-    <string name="rotate">Rotate</string>
-
-    <!-- Name for the photo effect that adjusts color saturation. [CHAR LIMIT=15] -->
-    <string name="saturation">Saturation</string>
-
-    <!-- Name for the photo effect that applies sepia color toning. [CHAR LIMIT=15] -->
-    <string name="sepia">Sepia</string>
-
-    <!-- Name for the photo effect that adds shadows. [CHAR LIMIT=15] -->
-    <string name="shadow">Shadows</string>
-
-    <!-- Name for the photo effect that sharpens blurred photo. [CHAR LIMIT=15] -->
-    <string name="sharpen">Sharpen</string>
-
-    <!-- Name for the photo effect that straightens crooked photo. [CHAR LIMIT=15] -->
-    <string name="straighten">Straighten</string>
-
-    <!-- Name for the photo effect that adjusts warmth color-temperature. [CHAR LIMIT=15] -->
-    <string name="temperature">Warmth</string>
-
-    <!-- Name for the photo effect that tints photo with various colors. [CHAR LIMIT=15] -->
-    <string name="tint">Tint</string>
-
-    <!-- Name for the photo effect that fades off photo edges. [CHAR LIMIT=15] -->
-    <string name="vignette">Vignette</string>
-
-    <!-- Tool-tip toast shown when the user chooses to crop photo. [CHAR LIMIT=40] -->
-    <string name="crop_tooltip">Drag markers to crop photo</string>
-
-    <!-- Tool-tip toast shown when the user chooses to doodle on photo. [CHAR LIMIT=40] -->
-    <string name="doodle_tooltip">Draw on photo to doodle</string>
-
-    <!-- Tool-tip toast shown when the user chooses to flip photo. [CHAR LIMIT=40] -->
-    <string name="flip_tooltip">Drag photo to flip it</string>
-
-    <!-- Tool-tip toast shown when the user chooses to remove red eyes. [CHAR LIMIT=40] -->
-    <string name="redeye_tooltip">Tap on red eyes to remove them</string>
-
-    <!-- Tool-tip toast shown when the user chooses to rotate photo. [CHAR LIMIT=40] -->
-    <string name="rotate_tooltip">Drag photo to rotate it</string>
-
-    <!-- Tool-tip toast shown when the user chooses to straighten photo. [CHAR LIMIT=40] -->
-    <string name="straighten_tooltip">Drag photo to straighten it</string>
-
-    <!-- Name for the exposure adjustment button in photo effects editor menu. [CHAR LIMIT=40] -->
-    <string name="photoeditor_exposure">Exposure effects</string>
-
-    <!-- Name for the color adjustment button in photo effects editor menu. [CHAR LIMIT=40] -->
-    <string name="photoeditor_color">Color effects</string>
-
-    <!-- Name for the artistic effect button in photo effects editor menu. [CHAR LIMIT=40] -->
-    <string name="photoeditor_artistic">Artistic effects</string>
-
-    <!-- Name for the fix up button in photo effects editor menu. [CHAR LIMIT=40] -->
-    <string name="photoeditor_fix">Fix up</string>
-
-    <!-- Name for the undo button in photo editor action bar. [CHAR LIMIT=40] -->
-    <string name="photoeditor_undo">Undo</string>
-
-    <!-- Name for the redo button in photo editor action bar. [CHAR LIMIT=40] -->
-    <string name="photoeditor_redo">Redo</string>
-
-</resources>
diff --git a/res/values/photoeditor_styles.xml b/res/values/photoeditor_styles.xml
deleted file mode 100644
index c02296d..0000000
--- a/res/values/photoeditor_styles.xml
+++ /dev/null
@@ -1,151 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 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.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="EffectIcon">
-        <item name="android:layout_width">@dimen/effect_icon_size</item>
-        <item name="android:layout_height">@dimen/effect_icon_size</item>
-        <item name="android:layout_gravity">center_horizontal</item>
-        <item name="android:scaleType">fitCenter</item>
-        <item name="android:background">@null</item>
-    </style>
-    <style name="EffectLabel">
-        <item name="android:layout_width">@dimen/effect_label_width</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_gravity">center_horizontal</item>
-        <item name="android:layout_marginTop">@dimen/effect_label_margin_top</item>
-        <item name="android:gravity">center</item>
-        <item name="android:textSize">@dimen/effect_label_text_size</item>
-        <item name="android:textColor">#FFFFFF</item>
-        <item name="android:maxLines">1</item>
-        <item name="android:shadowColor">#000000</item>
-        <item name="android:shadowRadius">2.0</item>
-    </style>
-    <style name="Effect">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:paddingLeft">@dimen/effect_padding_horizontal</item>
-        <item name="android:paddingRight">@dimen/effect_padding_horizontal</item>
-        <item name="android:orientation">vertical</item>
-    </style>
-    <style name="EffectsContainer">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:padding">@dimen/effects_container_padding</item>
-        <item name="android:orientation">horizontal</item>
-    </style>
-    <style name="EffectsBar">
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_alignParentBottom">true</item>
-        <item name="android:orientation">vertical</item>
-    </style>
-    <style name="EffectLabelInToolPanel" parent="@style/EffectLabel">
-        <item name="android:layout_marginTop">0dp</item>
-    </style>
-    <style name="SeekBar">
-        <item name="android:layout_width">@dimen/seekbar_width</item>
-        <item name="android:layout_height">@dimen/seekbar_height</item>
-        <item name="android:paddingTop">@dimen/seekbar_padding_vertical</item>
-        <item name="android:paddingBottom">@dimen/seekbar_padding_vertical</item>
-        <item name="android:paddingLeft">@dimen/seekbar_padding_horizontal</item>
-        <item name="android:paddingRight">@dimen/seekbar_padding_horizontal</item>
-        <item name="android:minHeight">@dimen/seekbar_height</item>
-        <item name="android:maxHeight">@dimen/seekbar_height</item>
-        <item name="android:background">@android:color/transparent</item>
-    </style>
-    <style name="ActionBarInner">
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">?android:attr/actionBarSize</item>
-    </style>
-    <style name="ActionBarOuter">
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">wrap_content</item>
-    </style>
-    <style name="ActionBarLinearLayout">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">fill_parent</item>
-        <item name="android:orientation">horizontal</item>
-    </style>
-    <style name="ActionBarBackLinearLayout" parent="@style/ActionBarLinearLayout">
-        <item name="android:clickable">true</item>
-        <item name="android:focusable">true</item>
-        <item name="android:background">?android:attr/selectableItemBackground</item>
-    </style>
-    <style name="ActionBarImageView">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">fill_parent</item>
-        <item name="android:layout_gravity">center</item>
-        <item name="android:scaleType">centerInside</item>
-        <item name="android:adjustViewBounds">true</item>
-    </style>
-    <style name="ActionBarArrow">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_gravity">center_vertical</item>
-        <item name="android:layout_marginLeft">@dimen/action_bar_arrow_margin_left</item>
-        <item name="android:layout_marginRight">@dimen/action_bar_arrow_margin_right</item>
-        <item name="android:src">?android:attr/homeAsUpIndicator</item>
-    </style>
-    <style name="ActionBarIcon" parent="@style/ActionBarImageView">
-        <item name="android:paddingTop">@dimen/action_bar_icon_padding_vertical</item>
-        <item name="android:paddingBottom">@dimen/action_bar_icon_padding_vertical</item>
-        <item name="android:paddingLeft">@dimen/action_bar_icon_padding_left</item>
-        <item name="android:paddingRight">@dimen/action_bar_icon_padding_right</item>
-        <item name="android:src">@mipmap/ic_launcher_gallery</item>
-    </style>
-    <style name="ActionBarTitle" parent="android:TextAppearance.Holo.Widget.ActionBar.Title">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">fill_parent</item>
-        <item name="android:gravity">center</item>
-    </style>
-    <style name="ImageActionButton" parent="@style/ActionBarImageView">
-        <item name="android:paddingTop">@dimen/action_button_padding_vertical</item>
-        <item name="android:paddingBottom">@dimen/action_button_padding_vertical</item>
-        <item name="android:paddingLeft">@dimen/action_button_padding_horizontal</item>
-        <item name="android:paddingRight">@dimen/action_button_padding_horizontal</item>
-        <item name="android:background">?android:attr/selectableItemBackground</item>
-    </style>
-    <style name="TextActionButton" parent="android:TextAppearance.Holo.Widget.ActionBar.Menu">
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">fill_parent</item>
-        <item name="android:gravity">center</item>
-        <item name="android:paddingLeft">@dimen/action_button_padding_horizontal</item>
-        <item name="android:paddingRight">@dimen/action_button_padding_horizontal</item>
-        <item name="android:background">?android:attr/selectableItemBackground</item>
-    </style>
-    <style name="EffectsMenuContainer" parent="@style/ActionBarLinearLayout">
-        <item name="android:layout_width">@dimen/effects_menu_container_width</item>
-        <item name="android:layout_gravity">center_horizontal</item>
-    </style>
-    <style name="EffectsMenuActionButton" parent="@style/ImageActionButton">
-        <item name="android:layout_weight">1</item>
-        <item name="android:background">@drawable/photoeditor_toggle_button_background</item>
-    </style>
-    <style name="FullscreenToolView">
-        <item name="android:layout_width">fill_parent</item>
-        <item name="android:layout_height">fill_parent</item>
-    </style>
-    <style name="SpinnerProgressDialog">
-        <item name="android:background">@android:color/transparent</item>
-        <item name="android:windowBackground">@android:color/transparent</item>
-        <item name="android:windowFrame">@null</item>
-        <item name="android:windowTitleStyle">@null</item>
-        <item name="android:windowIsFloating">true</item>
-        <item name="android:backgroundDimEnabled">false</item>
-    </style>
-</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d62bfdc..cb4bc18 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -167,10 +167,14 @@
     <!-- Toast message prompted when the specified item is not found [CHAR LIMIT=40]-->
     <string name="no_such_item">Couldn\'t find item.</string>
 
-    <!-- String used as a menu label. The suer can choose to edit the image
+    <!-- String used as a menu label. The user can choose to edit the image
          [CHAR_LIMIT=20]-->
     <string name="edit">Edit</string>
 
+    <!-- String used as a menu label. The user can choose to edit the image
+         [CHAR_LIMIT=20]-->
+    <string name="simple_edit">Simple Edit</string>
+
     <!-- String used as a title of a progress dialog. The user can
          choose to cache some Picasa picture albums on device, so it can
          be viewed offline. This string is shown when the request is being
@@ -183,11 +187,18 @@
          offline. [CHAR LIMIT=15] -->
     <string name="caching_label">Caching\u2026</string>
 
+    <!-- The title of the menu item to let user crop the image. [CHAR LIMIT=15] -->
     <string name="crop_action">Crop</string>
+    <!-- The title of the menu item to let user trim the video. [CHAR LIMIT=15] -->
     <string name="trim_action">Trim</string>
+    <!-- The title of the menu item to let user mute the video. [CHAR LIMIT=15] -->
+    <string name="mute_action">Mute</string>
+    <!-- The title of the menu item to let user set the image as background etc. [CHAR LIMIT=15] -->
     <string name="set_as">Set as</string>
 
-    <!-- String indicating an approximate location eg. Around Palo Alto, CA -->
+    <!-- String indicating an error when muting the video. [CHAR LIMIT=30] -->
+    <string name="video_mute_err">Can\'t mute video.</string>
+    <!-- String indicating an error when playing the video. [CHAR LIMIT=30] -->
     <string name="video_err">Can\'t play video.</string>
 
     <!-- Strings for grouping operations in the menu. The photos can be grouped
@@ -327,25 +338,30 @@
     <!-- String indicating camera flash is not used. [CHAR LIMIT=14] -->
     <string name="flash_off">No flash</string>
 
-    <!-- String for the empty not filtered image[CHAR LIMIT=14] -->
+    <!-- String indicating image width or height is unknown. [CHAR LIMIT=14] -->
+    <string name="unknown">Unknown</string>
+
+    <!-- String for the empty not filtered image [CHAR LIMIT=10] -->
     <string name="ffx_original">Original</string>
-   <!-- String for filter filtershow_fx_0000_vintage [CHAR LIMIT=14] -->
+    <!-- String for brown-colored old-fashion looking filter (filtershow_fx_0000_vintage) [CHAR LIMIT=10] -->
     <string name="ffx_vintage">Vintage</string>
-    <!-- String for filter filtershow_fx_0001_instant [CHAR LIMIT=14] -->
+    <!-- String for filter that brightens colors (filtershow_fx_0001_instant) [CHAR LIMIT=10] -->
     <string name="ffx_instant">Instant</string>
-    <!-- String for filter filtershow_fx_0002_bleach [CHAR LIMIT=14] -->
+    <!-- String for filter that washes out colors (filtershow_fx_0002_bleach) [CHAR LIMIT=10] -->
     <string name="ffx_bleach">Bleach</string>
-    <!-- String for filter filtershow_fx_0003_blue_crush [CHAR LIMIT=14] -->
+    <!-- String for filter that makes colors a bluish (filtershow_fx_0003_blue_crush) [CHAR LIMIT=10] -->
     <string name="ffx_blue_crush">Blue</string>
-    <!-- String for filter filtershow_fx_0004_bw_contrast [CHAR LIMIT=14] -->
+    <!-- String for filter that makes image black & white (filtershow_fx_0004_bw_contrast) [CHAR LIMIT=10] -->
     <string name="ffx_bw_contrast">B/W</string>
-    <!-- String for filter filtershow_fx_0005_punch [CHAR LIMIT=14] -->
+    <!-- String for filter that makes colors a yellowish (filtershow_fx_0005_punch) [CHAR LIMIT=10] -->
     <string name="ffx_punch">Punch</string>
-    <!-- String for filter filtershow_fx_0006_x_process [CHAR LIMIT=14] -->
+    <!-- String for filter that mimics the cross-process technique in
+         photography (makes colors bluish) (filtershow_fx_0006_x_process) [CHAR LIMIT=10] -->
     <string name="ffx_x_process">X Process</string>
-    <!-- String for filter filtershow_fx_0007_washout [CHAR LIMIT=14] -->
+    <!-- String for filter that makes image coffee-colored (filtershow_fx_0007_washout) [CHAR LIMIT=10] -->
     <string name="ffx_washout">Latte</string>
-    <!-- String for filter filtershow_fx_0008_washout_color [CHAR LIMIT=14] -->
+    <!-- String for filter that makes colors washed out and brownish
+         (filtershow_fx_0008_washout_color) [CHAR LIMIT=10] -->
     <string name="ffx_washout_color">Litho</string>
 
     <!-- Toast message shown after we make some album(s) available offline [CHAR LIMIT=50] -->
@@ -491,15 +507,20 @@
     <!-- Label for album grid button -->
     <string name="switch_photo_grid">Grid view</string>
 
+    <!-- Label for fullscreen button. [CHAR LIMIT=20] -->
+    <string name="switch_photo_fullscreen">Fullscreen view</string>
+
     <!-- The tilte of a dialog showing trimming in progress. [CHAR LIMIT=20] -->
     <string name="trimming">Trimming</string>
 
+    <!-- The tilte of a dialog showing muting in progress. [CHAR LIMIT=20] -->
+    <string name="muting">Muting</string>
+
     <!-- The content of a dialog showing trimming in progress. [CHAR LIMIT=30] -->
     <string name="please_wait">Please wait</string>
 
-    <!-- Toast after the trimming is done. [CHAR LIMIT=50] -->
-    <!-- TODO: this should be a format string!-->
-    <string name="save_into">Saving trimmed video into album :</string>
+    <!-- Toast after the trimming / muting is done. [CHAR LIMIT=50] -->
+    <string name="save_into">Saving video to <xliff:g id="album_name">%1$s</xliff:g> \u2026</string>
 
     <!-- Toast if the trimmed video is too short to trim. [CHAR LIMIT=80] -->
     <string name="trim_too_short">Can not trim : target video is too short</string>
@@ -509,4 +530,574 @@
 
     <!-- The label on the button that will save an edited image -->
     <string name="save" msgid="8140440041190264400">Save</string>
+
+    <!--  Text of notification message which is shown when user attaches camera -->
+    <string name="ingest_scanning" msgid="2048262851775139720">Scanning content...</string>
+
+    <!-- String indicating how many media items from the camera have been scanned -->
+    <plurals name="ingest_number_of_items_scanned">
+        <item quantity="zero">%1$d items scanned</item>
+        <item quantity="one">%1$d item scanned</item>
+        <item quantity="other">%1$d items scanned</item>
+    </plurals>
+
+    <!--  Status message shown when content from the camera is being sorted -->
+    <string name="ingest_sorting" msgid="624687230903648118">Sorting...</string>
+
+    <!--  Status message shown when scanning the content from the camera has completed -->
+    <string name="ingest_scanning_done">Scanning done</string>
+
+    <!--  Status message shown when content from an external camera is being imported -->
+    <string name="ingest_importing">Importing...</string>
+
+    <!--  Status message shown when there is no content available to be imported -->
+    <string name="ingest_empty_device">There is no content available for importing on this device.</string>
+
+    <!--  Status message shown when there is no MTP device connected  -->
+    <string name="ingest_no_device">There is no MTP device connected</string>
+
+    <!-- Camera resources below -->
+
+    <!-- General strings -->
+
+    <!-- title for the dialog showing the error of camera hardware -->
+    <string name="camera_error_title">Camera error</string>
+
+    <!-- message for the dialog showing the error of camera hardware -->
+    <string name="cannot_connect_camera">Can\'t connect to the camera.</string>
+
+    <!-- message for the dialog showing the camera is disabled because of security policies. Camera cannot be used. -->
+    <string name="camera_disabled">Camera has been disabled because of security policies.</string>
+
+    <!-- label for the icon meaning 'show me all the images that were taken with the camera' -->
+    <string name="camera_label">Camera</string>
+
+    <!-- label for the 'video recording application shown in the top level 'all applications' -->
+    <string name="video_camera_label">Camcorder</string>
+
+    <!-- alert to the user to wait for some operation to complete -->
+    <string name="wait">Please wait\u2026</string>
+
+    <!-- alert to the user that USB storage must be available before using the camera [CHAR LIMIT=NONE] -->
+    <string name="no_storage" product="nosdcard">Mount USB storage before using the camera.</string>
+    <!-- alert to the user that an SD card must be installed before using the camera -->
+    <string name="no_storage" product="default">Insert an SD card before using the camera.</string>
+
+    <!-- alert to the user that the USB storage is being disk-checked [CHAR LIMIT=30] -->
+    <string name="preparing_sd" product="nosdcard">Preparing USB storage\u2026</string>
+    <!-- alert to the user that the SD card is being disk-checked -->
+    <string name="preparing_sd" product="default">Preparing SD card\u2026</string>
+
+    <!-- alert to the user that the camera fails to read or write the USB storage. [CHAR LIMIT=NONE] -->
+    <string name="access_sd_fail" product="nosdcard">Couldn\'t access USB storage.</string>
+    <!-- alert to the user that the camera fails to read or write the SD card. -->
+    <string name="access_sd_fail" product="default">Couldn\'t access SD card.</string>
+
+    <!-- button in review mode indicating that the photo taking, video recording, and panorama saving session should be canceled [CHAR LIMIT=10] -->
+    <string name="review_cancel">CANCEL</string>
+
+    <!-- button in review mode indicating that the taken photo/video is OK to be attached/uploaded [CHAR LIMIT=10] -->
+    <string name="review_ok">DONE</string>
+
+    <!-- A label that overlays on top of the preview frame to indicate the camcorder is in time lapse mode [CHAR LIMIT=35] -->
+    <string name="time_lapse_title">Time lapse recording</string>
+
+    <!-- Settings screen, camera selection dialog title. Users can select a camera from the phone (front-facing or back-facing). [CHAR LIMIT=20] -->
+    <string name="pref_camera_id_title">Choose camera</string>
+
+    <string name="pref_camera_id_default" translatable="false">0</string>
+
+    <!-- In select camera setting, back facing camera. [CHAR LIMIT=14] -->
+    <string name="pref_camera_id_entry_back">Back</string>
+    <!-- In select camera setting, front-facing camera. [CHAR LIMIT=14] -->
+    <string name="pref_camera_id_entry_front">Front</string>
+
+    <!-- Settings screen, setting title text -->
+    <string name="pref_camera_recordlocation_title">Store location</string>
+
+    <string name="pref_camera_recordlocation_default" translatable="false">none</string>
+
+    <!--  Label for record location preference [CHAR LIMIT=50] -->
+    <string name="pref_camera_location_label">LOCATION</string>
+
+    <!-- Title for countdown timer on camera settings screen [CHAR LIMIT=30]-->
+    <string name="pref_camera_timer_title">Countdown timer</string>
+
+    <string name="pref_camera_timer_default" translatable="false">0</string>
+    <!-- Entry for countdown timer setting. e.g. 1 second, 10 seconds, etc. [CHAR LIMIT=30]-->
+    <plurals name="pref_camera_timer_entry">
+        <item quantity="one">1 second</item>
+        <item quantity="other">%d seconds</item>
+    </plurals>
+    <string name="pref_camera_timer_sound_default">@string/setting_on_value</string>
+    <!-- Text followed by a checkbox to turn on/off sound effects during the countdown. [CHAR LIMIT = 16]-->
+    <string name="pref_camera_timer_sound_title">Beep during countdown</string>
+
+    <!-- Entry of a on/off setting. The setting is turned off. [CHAR LIMIT=15] -->
+    <string name="setting_off">Off</string>
+    <!-- Entry of a on/off setting. The setting is turned on. [CHAR LIMIT=15] -->
+    <string name="setting_on">On</string>
+
+    <!-- The value of a camera preference indicating the setting is off. -->
+    <string name="setting_off_value" translatable="false">off</string>
+    <!-- The value of a camera preference indicating the setting is on. -->
+    <string name="setting_on_value" translatable="false">on</string>
+
+    <!-- The Video quality settings in preference [CHAR LIMIT=21] -->
+    <string name="pref_video_quality_title">Video quality</string>
+    <!-- The default quality value is 5 (720p) -->
+    <string name="pref_video_quality_default" translatable="false">5</string>
+    <!-- Video quality setting entry. Videos will be recorded in 1080p quality. [CHAR LIMIT=24] -->
+    <string name="pref_video_quality_entry_1080p" translatable="false">HD 1080p</string>
+    <!-- Video quality setting entry. Videos will be recorded in 720p quality. [CHAR LIMIT=24] -->
+    <string name="pref_video_quality_entry_720p" translatable="false">HD 720p</string>
+    <!-- Video quality setting entry. Videos will be recorded in 480p quality. [CHAR LIMIT=24] -->
+    <string name="pref_video_quality_entry_480p" translatable="false">SD 480p</string>
+    <!-- Video quality setting entry. Videos will be recorded in the highest quality available on the device. [CHAR LIMIT=24] -->
+    <string name="pref_video_quality_entry_high">High</string>
+    <!-- Video quality setting entry. Videos will be recorded in the lowest quality available on the device. [CHAR LIMIT=24] -->
+    <string name="pref_video_quality_entry_low">Low</string>
+
+    <!-- Describes the preference dialog for choosing interval between frame capture for
+    time lapse recording. Appears at top of the dialog. [CHAR LIMIT=30] -->
+    <string name="pref_video_time_lapse_frame_interval_title">Time lapse</string>
+    <string name="pref_video_time_lapse_frame_interval_default" translatable="false">0</string>
+
+    <!-- Settings screen, Camera setting category title -->
+    <string name="pref_camera_settings_category">Camera settings</string>
+
+    <!-- Settings screen, Camcorder setting category title -->
+    <string name="pref_camcorder_settings_category">Camcorder settings</string>
+
+    <!-- Settings screen, Picture size title -->
+    <string name="pref_camera_picturesize_title">Picture size</string>
+
+    <!-- Settings screen, dialog choice for 13 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_13mp">13M pixels</string>
+    <!-- Settings screen, dialog choice for 8 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_8mp">8M pixels</string>
+    <!-- Settings screen, dialog choice for 5 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_5mp">5M pixels</string>
+    <!-- Settings screen, dialog choice for 4 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_4mp">4M pixels</string>
+    <!-- Settings screen, dialog choice for 3 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_3mp">3M pixels</string>
+    <!-- Settings screen, dialog choice for 2 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_2mp">2M pixels</string>
+    <!-- Settings screen, dialog choice for 2 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_2mp_wide">2M pixels (16:9)</string>
+    <!-- Settings screen, dialog choice for 1.3 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_1_3mp">1.3M pixels</string>
+    <!-- Settings screen, dialog choice for 1 megapixels picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_1mp">1M pixels</string>
+    <!-- Settings screen, dialog choice for VGA picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_vga">VGA</string>
+    <!-- Settings screen, dialog choice for QVGA picture size [CHAR LIMIT=15] -->
+    <string name="pref_camera_picturesize_entry_qvga">QVGA</string>
+
+    <!-- Settings screen, Focus mode title -->
+    <string name="pref_camera_focusmode_title">Focus mode</string>
+
+    <!-- Settings screen, Focus mode dialog radio button choices -->
+    <string name="pref_camera_focusmode_entry_auto">Auto</string>
+    <string name="pref_camera_focusmode_entry_infinity">Infinity</string>
+    <string name="pref_camera_focusmode_entry_macro">Macro</string>
+
+    <!-- Menu, focus mode labels [CHAR LIMIT=50] -->
+    <string name="pref_camera_focusmode_label_auto">AUTO</string>
+    <string name="pref_camera_focusmode_label_infinity">INFINITY</string>
+    <string name="pref_camera_focusmode_label_macro">MACRO</string>
+
+    <!-- Default flash mode setting.-->
+    <string name="pref_camera_flashmode_default" translatable="false">auto</string>
+
+    <!-- Value for flash off setting-->
+    <string name="pref_camera_flashmode_no_flash" translatable="false">no_flash</string>
+
+    <!-- Settings screen, Flash mode title -->
+    <string name="pref_camera_flashmode_title">Flash mode</string>
+        <!-- flash label [CHAR LIMIT=50] -->
+    <string name="pref_camera_flashmode_label">FLASH MODE</string>
+
+    <!-- Settings screen, Flash mode dialog radio button choices -->
+    <string name="pref_camera_flashmode_entry_auto">Auto</string>
+    <string name="pref_camera_flashmode_entry_on">On</string>
+    <string name="pref_camera_flashmode_entry_off">Off</string>
+
+    <!-- Menu, flash mode labels [CHAR LIMIT=50] -->
+    <string name="pref_camera_flashmode_label_auto">FLASH AUTO</string>
+    <string name="pref_camera_flashmode_label_on">FLASH ON</string>
+    <string name="pref_camera_flashmode_label_off">FLASH OFF</string>
+
+    <!-- Default videocamera flash mode setting.-->
+    <string name="pref_camera_video_flashmode_default" translatable="false">off</string>
+
+    <!-- Default white balance setting. -->
+    <string name="pref_camera_whitebalance_default" translatable="false">auto</string>
+
+    <!-- Settings screen, white balance title -->
+    <string name="pref_camera_whitebalance_title">White balance</string>
+    <!-- Menu, white balance label -->
+    <string name="pref_camera_whitebalance_label">WHITE BALANCE</string>
+
+    <!-- Settings screen, White balance dialog radio button choices -->
+    <string name="pref_camera_whitebalance_entry_auto">Auto</string>
+    <string name="pref_camera_whitebalance_entry_incandescent">Incandescent</string>
+    <string name="pref_camera_whitebalance_entry_daylight">Daylight</string>
+    <string name="pref_camera_whitebalance_entry_fluorescent">Fluorescent</string>
+    <string name="pref_camera_whitebalance_entry_cloudy">Cloudy</string>
+
+    <!-- Menu, White balance labels [CHAR LIMIT=50] -->
+    <string name="pref_camera_whitebalance_label_auto">AUTO</string>
+    <string name="pref_camera_whitebalance_label_incandescent">INCANDESCENT</string>
+    <string name="pref_camera_whitebalance_label_daylight">DAYLIGHT</string>
+    <string name="pref_camera_whitebalance_label_fluorescent">FLUORESCENT</string>
+    <string name="pref_camera_whitebalance_label_cloudy">CLOUDY</string>
+
+    <!-- Default scene mode setting. -->
+    <string name="pref_camera_scenemode_default" translatable="false">auto</string>
+
+    <!-- Settings screen, Select Scene mode -->
+    <string name="pref_camera_scenemode_title">Scene mode</string>
+
+    <!-- Settings menu, scene mode choices [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_auto">Auto</string>
+    <!-- Scene mode that uses HDR (high dynamic range) [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_hdr">HDR</string>
+    <!-- Scene mode that takes an image quickly with little motion blur. [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_action">Action</string>
+    <!-- Scene mode that takes long exposures to capture night scenes without flash. [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_night">Night</string>
+    <!-- Scene mode optimized for taking images in the sunset. [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_sunset">Sunset</string>
+    <!-- Scene mode optimized for taking indoor low-lights pictures. [CHAR LIMIT=16] -->
+    <string name="pref_camera_scenemode_entry_party">Party</string>
+
+    <!-- Settings menu, scene mode labels [CHAR LIMIT=50] -->
+    <string name="pref_camera_scenemode_label_auto">NONE</string>
+    <!-- Scene mode that takes an image quickly with little motion blur. [CHAR LIMIT=50] -->
+    <string name="pref_camera_scenemode_label_action">ACTION</string>
+    <!-- Scene mode that takes long exposures to capture night scenes without flash. [CHAR LIMIT=50] -->
+    <string name="pref_camera_scenemode_label_night">NIGHT</string>
+    <!-- Scene mode optimized for taking images in the sunset. [CHAR LIMIT=50] -->
+    <string name="pref_camera_scenemode_label_sunset">SUNSET</string>
+    <!-- Scene mode optimized for taking indoor low-lights pictures. [CHAR LIMIT=50] -->
+    <string name="pref_camera_scenemode_label_party">PARTY</string>
+
+    <!-- Settings menu countdown timer labels [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label">COUNTDOWN TIMER</string>
+    <!-- Settings menu countdown timer off [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label_off">TIMER OFF</string>
+    <!-- Settings menu countdown timer 1 second [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label_one">1 SECOND</string>
+    <!-- Settings menu countdown timer 3 seconds [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label_three">3 SECONDS</string>
+    <!-- Settings menu countdown timer 10 seconds [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label_ten">10 SECONDS</string>
+    <!-- Settings menu countdown timer 15 seconds [CHAR LIMIT=50] -->
+    <string name="pref_camera_countdown_label_fifteen">15 SECONDS</string>
+
+    <!-- Toast after trying to select a setting that is not allowed to change in scene mode [CHAR LIMIT=NONE] -->
+    <string name="not_selectable_in_scene_mode">Not selectable in scene mode.</string>
+
+    <!-- Exposure settings in preference -->
+    <string name="pref_exposure_title">Exposure</string>
+    <string name="pref_exposure_default" translatable="false">0</string>
+    <!--  menu label exposure compensation [CHAR LIMIT=50] -->
+    <string name="pref_exposure_label">EXPOSURE</string>
+
+    <!-- Default HDR entry value -->
+    <string name="pref_camera_hdr_default">@string/setting_off_value</string>
+
+    <!-- HDR label ON [CHAR LIMIT=60] -->
+    <string name="pref_camera_hdr_label">HDR</string>
+
+    <!-- switch camera label back [CHAR LIMIT=60] -->
+    <string name="pref_camera_id_label_back">FRONT CAMERA</string>
+    <!-- switch camera label front [CHAR LIMIT=60] -->
+    <string name="pref_camera_id_label_front">BACK CAMERA</string>
+
+    <!-- Dialog "OK" button. Dismisses dialog. -->
+    <string name="dialog_ok">OK</string>
+
+    <!-- Low-memory dialog message [CHAR LIMT=NONE] -->
+    <string name="spaceIsLow_content" product="nosdcard">Your USB storage is running out of space. Change the quality setting or delete some images or other files.</string>
+    <!-- Low-memory dialog message [CHAR LIMIT=NONE] -->
+    <string name="spaceIsLow_content" product="default">Your SD card is running out of space. Change the quality setting or delete some images or other files.</string>
+
+    <!-- Camera format string for new image files. Passed to java.text.SimpleDateFormat. -->
+    <string name="image_file_name_format" translatable="false">"'IMG'_yyyyMMdd_HHmmss"</string>
+
+    <!-- Video Camera format string for new video files. Passed to java.text.SimpleDateFormat. -->
+    <string name="video_file_name_format" translatable="false">"'VID'_yyyyMMdd_HHmmss"</string>
+
+    <!-- Filename prefix for panorama output. -->
+    <string name="pano_file_name_format" translatable="false">"'PANO'_yyyyMMdd_HHmmss"</string>
+
+    <!-- The message shown when video record reaches size limit. -->
+    <string name="video_reach_size_limit">Size limit reached.</string>
+
+    <!-- The text shown when the panorama panning speed is to fast [CHAR LIMIT=12] -->
+    <string name="pano_too_fast_prompt">Too fast</string>
+
+    <!-- The text shown in the progress dialog when panorama preview is generating in the background [CHAR LIMIT=30] -->
+    <string name="pano_dialog_prepare_preview">Preparing panorama</string>
+
+    <!-- The text shown in the dialog when panorama saving failed [CHAR LIMIT=40] -->
+    <string name="pano_dialog_panorama_failed">Couldn\'t save panorama.</string>
+
+    <!-- The text shown on the dialog title in the dialogs for Panorama [CHAR LIMIT=12] -->
+    <string name="pano_dialog_title">Panorama</string>
+
+    <!-- The text shown on the top-left corner of the screen to indicate the capturing is on going [CHAR LIMIT=27] -->
+    <string name="pano_capture_indication">Capturing panorama</string>
+
+    <!-- The text shown in the progress dialog when waiting for previous panorama finishing [CHAR LIMIT=40] -->
+    <string name="pano_dialog_waiting_previous">Waiting for previous panorama</string>
+
+    <!-- The text shown on the bottom-left corner of the screen to indicate that the saving is in process [CHAR LIMIT=13] -->
+    <string name="pano_review_saving_indication_str">Saving\u2026</string>
+
+    <!-- The text shown on the screen to indicate that the panorama is rendering [CHAR LIMIT=27] -->
+    <string name="pano_review_rendering">Rendering panorama</string>
+
+    <!-- Toast telling users tapping on the viewfinder will trigger autofocus [CHAR LIMIT=28] -->
+    <string name="tap_to_focus">Touch to focus.</string>
+
+    <!-- Default effect setting that clears the effect. -->
+    <string name="pref_video_effect_default" translatable="false">none</string>
+
+    <!-- Title of video effect setting popup window -->
+    <string name="pref_video_effect_title">Effects</string>
+
+    <!-- Effect setting item that clear the effect. [CHAR LIMIT=14] -->
+    <string name="effect_none">None</string>
+    <!-- Effect setting item that squeezes the face. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_squeeze">Squeeze</string>
+    <!-- Effect setting item that makes eyes big. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_big_eyes">Big eyes</string>
+    <!-- Effect setting item that makes mouth big. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_big_mouth">Big mouth</string>
+    <!-- Effect setting item that makes mouth small. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_small_mouth">Small mouth</string>
+    <!-- Effect setting item that makes nose big. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_big_nose">Big nose</string>
+    <!-- Effect setting item that makes eyes small. [CHAR LIMIT=14] -->
+    <string name="effect_goofy_face_small_eyes">Small eyes</string>
+    <!-- Effect setting item that replaces background with Android in Space. [CHAR LIMIT=14] -->
+    <string name="effect_backdropper_space">In space</string>
+    <!-- Effect setting item that replaces background with a sunset. [CHAR LIMIT=14] -->
+    <string name="effect_backdropper_sunset">Sunset</string>
+    <!-- Effect setting item that replaces background with video from gallery. [CHAR LIMIT=14] -->
+    <string name="effect_backdropper_gallery">Your video</string>
+
+    <!-- Message displayed in overlay during background replacement training [CHAR LIMIT=180]-->
+    <string name="bg_replacement_message">Set your device down.\nStep out of view for a moment.</string>
+
+
+    <!-- Toast telling users tapping on the viewfinder will take a picture [CHAR LIMIT=54] -->
+    <string name="video_snapshot_hint">Touch to take photo while recording.</string>
+
+    <!-- Announcement telling users video recording has just started [CHAR LIMIT=NONE] -->
+    <string name="video_recording_started">Video recording has started.</string>
+    <!-- Announcement telling users video recording has just stopped [CHAR LIMIT=NONE] -->
+    <string name="video_recording_stopped">Video recording has stopped.</string>
+
+    <!-- Toast telling users video snapshot is disabled when the effects are on and a user tries to tap on the viewfinder [CHAR LIMIT=65] -->
+    <string name="disable_video_snapshot_hint">Video snapshot is disabled when special effects are on.</string>
+
+    <!-- A button in effect setting popup to clear the effect. [CHAR LIMIT=26] -->
+    <string name="clear_effects">Clear effects</string>
+
+    <!-- Title of category for silly face effects. [CHAR LIMIT=26] -->
+    <string name="effect_silly_faces">SILLY FACES</string>
+
+    <!-- Title of category for background replacement effects. [CHAR LIMIT=26] -->
+    <string name="effect_background">BACKGROUND</string>
+
+    <!-- The shutter button. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_shutter_button">Shutter button</string>
+    <!-- The menu button. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_menu_button">Menu button</string>
+    <!-- The button to review the thumbnail. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_review_thumbnail">Most recent photo</string>
+    <!-- The front/back camera switch. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_camera_picker">Front and back camera switch</string>
+    <!-- The mode picker to switch between camera, video and panorama. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_mode_picker">Camera, video, or panorama selector</string>
+    <!-- The button to switch to the second-level indicators of the camera settings. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_second_level_indicators">More setting controls</string>
+    <!-- The button to back to the first-level indicators of the camera settings. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_back_to_first_level">Close setting controls</string>
+    <!-- The zoom control button. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_zoom_control">Zoom control</string>
+    <!-- The decrement button in camera preference such as exposure, picture size. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_decrement">Decrease %1$s</string>
+    <!-- The increment button in camera preference such as exposure, picture size. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_increment">Increase %1$s</string>
+    <!-- The check box in camera settings, such as store location. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_check_box">%1$s check box</string>
+    <!-- The button to switch to Camera mode. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_switch_to_camera">Switch to photo</string>
+    <!-- The button to switch to Video mode. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_switch_to_video">Switch to video</string>
+    <!-- The button to switch to Panorama mode. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_switch_to_panorama">Switch to panorama</string>
+    <!-- The button to switch to new Panorama mode. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_switch_to_new_panorama">Switch to new panorama</string>
+    <!-- The button in review mode indicating that the photo taking, video recording, and panorama saving session should be canceled [CHAR LIMIT = NONE] -->
+    <string name="accessibility_review_cancel">Review cancel</string>
+    <!-- The button in review mode indicating that the taken photo/video is OK to be attached/uploaded [CHAR LIMIT = NONE] -->
+    <string name="accessibility_review_ok">Review done</string>
+    <!-- button in review mode indicate the user want to retake another photo/video for attachment [
+CHAR LIMIT = NONE] -->
+    <string name="accessibility_review_retake">Review retake</string>
+    <!-- The button to play the video. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_play_video">Play video</string>
+    <!-- The button to pause the video. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_pause_video">Pause video</string>
+    <!-- The button to reload the video. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_reload_video">Reload video</string>
+    <!-- The time bar of the media player. [CHAR LIMIT = NONE] -->
+    <string name="accessibility_time_bar">Video player time bar</string>
+
+    <!-- TODO: remove the string as it is a work-around solution to bypass the default speak of the element type. -->
+    <string name="empty" translatable="false">" "</string>
+
+    <!-- Default text for a button that can be toggled on and off. -->
+    <string name="capital_on">ON</string>
+    <!-- Default text for a button that can be toggled on and off. -->
+    <string name="capital_off">OFF</string>
+
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_off">Off</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_500">0.5 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_1000">1 second</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_1500">1.5 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_2000">2 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_2500">2.5 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_3000">3 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_4000">4 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_5000">5 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_6000">6 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_10000">10 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_12000">12 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_15000">15 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_24000">24 seconds</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_30000">0.5 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_60000">1 minute</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_90000">1.5 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_120000">2 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_150000">2.5 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_180000">3 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_240000">4 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_300000">5 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_360000">6 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_600000">10 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_720000">12 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_900000">15 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_1440000">24 minutes</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_1800000">0.5 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_3600000">1 hour</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_5400000">1.5 hour</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_7200000">2 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_9000000">2.5 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_10800000">3 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_14400000">4 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_18000000">5 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_21600000">6 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_36000000">10 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_43200000">12 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_54000000">15 hours</string>
+    <!-- Text to indicate time lapse recording frame interval [CHAR LIMIT = 30] -->
+    <string name="pref_video_time_lapse_frame_interval_86400000">24 hours</string>
+
+    <!-- Seconds: a unit of time for time lapse intervals. [CHAR LIMIT = 20] -->
+    <string name="time_lapse_seconds">seconds</string>
+    <!-- Minutes: a unit of time for time lapse intervals. [CHAR LIMIT = 20] -->
+    <string name="time_lapse_minutes">minutes</string>
+    <!-- Hours: a unit of time for time lapse intervals. [CHAR LIMIT = 20] -->
+    <string name="time_lapse_hours">hours</string>
+
+    <!-- The button to confirm time-lapse setting changes. [CHAR LIMIT = 20] -->
+    <string name="time_lapse_interval_set">Done</string>
+    <!-- Title in time interval picker for setting time interval. [CHAR LIMIT = 30]-->
+    <string name="set_time_interval">Set Time Interval</string>
+    <!-- Help text that is shown when the time lapse feature is turned off. [CHAR LIMIT = 180]-->
+    <string name="set_time_interval_help">Time lapse feature is off. Turn it on to set time interval.</string>
+    <!-- Help text that is shown when the countdown timer is turned off. [CHAR LIMIT = 180]-->
+    <string name="set_timer_help">Countdown timer is off. Turn it on to count down before taking a picture.</string>
+    <!-- Title in timer setting for setting the duration for the countdown timer. [CHAR LIMIT = 50]-->
+    <string name="set_duration">Set duration in seconds</string>
+    <!-- On-screen hint during timer countdown for taking a photo. [CHAR LIMIT = 60]-->
+    <string name="count_down_title_text">Counting down to take a photo</string>
+
+    <!-- Title for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = 50] -->
+    <string name="remember_location_title">Remember photo locations?</string>
+    <!-- Message for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = None] -->
+    <string name="remember_location_prompt">Tag your photos and videos with the locations where they are taken.\n\nOther apps can access this information along with your saved images.</string>
+    <!-- Negative answer for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = 20] -->
+    <string name="remember_location_no">No thanks</string>
+    <!-- Positive answer for first run dialog asking if the user wants to remember photo locations [CHAR LIMIT = 20] -->
+    <string name="remember_location_yes">Yes</string>
+
+    <!-- Menu item to launch the camera app [CHAR LIMIT=25] -->
+    <string name="menu_camera">Camera</string>
+    <!-- Menu item to search for photos [CHAR LIMIT=25] -->
+    <string name="menu_search">Search</string>
+    <!-- Title for the all photos tab [CHAR LIMIT=25] -->
+    <string name="tab_photos">Photos</string>
+    <!-- Title for the albums tab [CHAR LIMIT=25] -->
+    <string name="tab_albums">Albums</string>
+
+    <!-- Camera menu labels -->
+
+    <!-- more options label [CHAR LIMIT=50] -->
+    <string name="camera_menu_more_label">MORE OPTIONS</string>
+    <!-- settings label [CHAR LIMIT=50] -->
+    <string name="camera_menu_settings_label">SETTINGS</string>
+
+    <!-- String indicating how many photos are in an album [CHAR LIMIT=15] -->
+    <plurals name="number_of_photos">
+        <item quantity="one">%1$d photo</item>
+        <item quantity="other">%1$d photos</item>
+    </plurals>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 8d3730d..e6179e9 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -48,4 +48,214 @@
     <color name="darker_transparent">#C1000000</color>
     <style name="ActionBarTwoLinePrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"></style>
     <style name="ActionBarTwoLineSecondary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Subtitle"></style>
+    <style name="ActionBarTwoLineItem">
+        <item name="android:background">@drawable/action_bar_two_line_background</item>
+    </style>
+
+    <!-- Camera resources below -->
+
+    <style name="Theme.Camera" parent="Theme.CameraBase">
+        <item name="android:windowBackground">@android:color/black</item>
+        <item name="android:colorBackground">@android:color/black</item>
+        <item name="android:colorBackgroundCacheHint">@android:color/black</item>
+    </style>
+    <style name="Theme.CameraBase" parent="android:Theme.Black.NoTitleBar.Fullscreen"/>
+    <style name="OnScreenHintTextAppearance">
+        <item name="android:textColor">@android:color/primary_text_dark</item>
+        <item name="android:textColorHighlight">#FFFF9200</item>
+        <item name="android:textColorHint">#808080</item>
+        <item name="android:textColorLink">#5C5CFF</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+    </style>
+    <style name="OnScreenHintTextAppearance.Small">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">@android:color/secondary_text_dark</item>
+    </style>
+    <style name="Animation_OnScreenHint">
+        <item name="android:windowEnterAnimation">@anim/on_screen_hint_enter</item>
+        <item name="android:windowExitAnimation">@anim/on_screen_hint_exit</item>
+    </style>
+    <style name="ReviewPlayIcon">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_centerInParent">true</item>
+        <item name="android:visibility">gone</item>
+        <item name="android:src">@drawable/ic_gallery_play_big</item>
+    </style>
+    <style name="PopupTitleSeparator">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">2dp</item>
+        <item name="android:background">@color/popup_title_color</item>
+    </style>
+    <style name="SettingItemList">
+        <item name="android:orientation">vertical</item>
+        <item name="android:paddingBottom">3dp</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:listSelector">@drawable/bg_pressed</item>
+    </style>
+    <style name="SettingItemTitle">
+        <item name="android:textSize">@dimen/setting_item_text_size</item>
+        <item name="android:gravity">left|center_vertical</item>
+        <item name="android:textColor">@color/primary_text</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+    <style name="SettingItemText">
+        <item name="android:layout_width">@dimen/setting_item_text_width</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">center_vertical</item>
+        <item name="android:gravity">center</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">@color/primary_text</item>
+        <item name="android:textSize">@dimen/setting_item_text_size</item>
+    </style>
+    <style name="SettingRow">
+        <item name="android:gravity">center_vertical</item>
+        <item name="android:orientation">horizontal</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/setting_row_height</item>
+        <item name="android:paddingLeft">@dimen/setting_item_list_margin</item>
+        <item name="android:paddingRight">@dimen/setting_item_list_margin</item>
+        <item name="android:background">@drawable/setting_picker</item>
+    </style>
+    <style name="OnViewfinderLabel">
+        <item name="android:gravity">center</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:layout_margin">10dp</item>
+        <item name="android:paddingLeft">15dp</item>
+        <item name="android:paddingRight">15dp</item>
+        <item name="android:paddingTop">3dp</item>
+        <item name="android:paddingBottom">3dp</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:textSize">16dp</item>
+        <item name="android:background">@drawable/bg_text_on_preview</item>
+    </style>
+    <style name="PanoCustomDialogText">
+        <item name="android:textAppearance">@android:style/TextAppearance.Medium</item>
+    </style>
+    <style name="EffectSettingGrid">
+        <item name="android:layout_marginLeft">@dimen/setting_item_list_margin</item>
+        <item name="android:layout_marginRight">@dimen/setting_item_list_margin</item>
+        <item name="android:paddingBottom">3dp</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:numColumns">3</item>
+        <item name="android:verticalSpacing">3dp</item>
+        <item name="android:horizontalSpacing">3dp</item>
+        <item name="android:choiceMode">singleChoice</item>
+    </style>
+    <style name="EffectSettingItem">
+        <item name="android:orientation">vertical</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">9dp</item>
+        <item name="android:paddingBottom">9dp</item>
+        <item name="android:paddingLeft">2dp</item>
+        <item name="android:paddingRight">2dp</item>
+        <item name="android:background">@drawable/setting_picker</item>
+    </style>
+    <style name="EffectSettingItemTitle">
+        <item name="android:textSize">@dimen/effect_setting_item_text_size</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingTop">1dp</item>
+    </style>
+    <style name="EffectSettingTypeTitle">
+        <item name="android:textSize">@dimen/effect_setting_type_text_size</item>
+        <item name="android:gravity">left|center_vertical</item>
+        <item name="android:textColor">@android:color/white</item>
+        <item name="android:alpha">0.7</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:minHeight">@dimen/effect_setting_type_text_min_height</item>
+        <item name="android:paddingLeft">@dimen/effect_setting_type_text_left_padding</item>
+    </style>
+    <style name="EffectTypeSeparator">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_marginLeft">8dp</item>
+        <item name="android:layout_marginRight">8dp</item>
+        <item name="android:layout_marginBottom">14dp</item>
+        <item name="android:layout_height">2dp</item>
+        <item name="android:background">#2c2c2c</item>
+    </style>
+    <style name="EffectTitleSeparator">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">2dp</item>
+        <item name="android:paddingBottom">4dp</item>
+        <item name="android:background">@android:drawable/divider_horizontal_dark</item>
+    </style>
+    <style name="TextAppearance.DialogWindowTitle" parent="">
+        <item name="android:textSize">22sp</item>
+        <item name="android:textColor">@color/holo_blue_light</item>
+    </style>
+    <style name="TextAppearance.Medium" parent="@android:style/TextAppearance.Medium"/>
+    <style name="Widget.Button.Borderless" parent="android:Widget.Button">
+        <item name="android:background">@drawable/bg_pressed</item>
+        <item name="android:textAppearance">@style/TextAppearance.Medium</item>
+        <item name="android:textColor">@color/primary_text</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:minWidth">64dip</item>
+        <item name="android:paddingLeft">4dip</item>
+        <item name="android:paddingRight">4dip</item>
+    </style>
+
+    <style name="ReviewControlText_xlarge">
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:background">@drawable/bg_pressed_exit_fading</item>
+        <item name="android:gravity">center</item>
+        <item name="android:paddingLeft">2dp</item>
+        <item name="android:paddingRight">10dp</item>
+        <item name="android:paddingTop">10dp</item>
+        <item name="android:paddingBottom">10dp</item>
+        <item name="android:textSize">18sp</item>
+        <item name="android:textStyle">bold</item>
+        <item name="android:clickable">true</item>
+        <item name="android:focusable">true</item>
+    </style>
+    <style name="PopupTitleText_xlarge">
+        <item name="android:textSize">@dimen/popup_title_text_size</item>
+        <item name="android:layout_gravity">left|center_vertical</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:singleLine">true</item>
+        <item name="android:textColor">@color/popup_title_color</item>
+        <item name="android:layout_marginLeft">10dp</item>
+    </style>
+    <style name="PanoCustomDialogText_xlarge">
+        <item name="android:textAppearance">@android:style/TextAppearance.Large</item>
+    </style>
+    <style name="ViewfinderLabelLayout_xlarge">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_margin">13dp</item>
+    </style>
+    <style name="SwitcherButton">
+        <item name="android:layout_width">@dimen/switcher_size</item>
+        <item name="android:layout_height">@dimen/switcher_size</item>
+        <item name="android:background">@drawable/bg_pressed_exit_fading</item>
+    </style>
+    <style name="MenuIndicator">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:enabled">false</item>
+        <item name="android:scaleType">center</item>
+    </style>
+        <style name="CameraControls">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">match_parent</item>
+    </style>
+
 </resources>
diff --git a/res/xml/camera_preferences.xml b/res/xml/camera_preferences.xml
new file mode 100644
index 0000000..8c13a34
--- /dev/null
+++ b/res/xml/camera_preferences.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<PreferenceGroup
+        xmlns:camera="http://schemas.android.com/apk/res/com.android.gallery3d"
+        camera:title="@string/pref_camera_settings_category">
+    <IconListPreference
+            camera:key="pref_camera_flashmode_key"
+            camera:defaultValue="@string/pref_camera_flashmode_default"
+            camera:title="@string/pref_camera_flashmode_title"
+            camera:icons="@array/camera_flashmode_icons"
+            camera:largeIcons="@array/camera_flashmode_largeicons"
+            camera:entries="@array/pref_camera_flashmode_entries"
+            camera:entryValues="@array/pref_camera_flashmode_entryvalues"
+            camera:labelList="@array/pref_camera_flashmode_labels" />
+    <IconListPreference
+            camera:key="pref_camera_exposure_key"
+            camera:defaultValue="@string/pref_exposure_default"
+            camera:title="@string/pref_exposure_title"
+            camera:singleIcon="@drawable/ic_exposure_holo_light" />
+    <IconListPreference
+            camera:key="pref_camera_scenemode_key"
+            camera:defaultValue="@string/pref_camera_scenemode_default"
+            camera:title="@string/pref_camera_scenemode_title"
+            camera:singleIcon="@drawable/ic_sce"
+            camera:entries="@array/pref_camera_scenemode_entries"
+            camera:labelList="@array/pref_camera_scenemode_labels"
+            camera:icons="@array/pref_camera_scenemode_icons"
+            camera:largeIcons="@array/pref_camera_scenemode_icons"
+            camera:entryValues="@array/pref_camera_scenemode_entryvalues" />
+    <IconListPreference
+            camera:key="pref_camera_whitebalance_key"
+            camera:defaultValue="@string/pref_camera_whitebalance_default"
+            camera:title="@string/pref_camera_whitebalance_title"
+            camera:icons="@array/whitebalance_icons"
+            camera:largeIcons="@array/whitebalance_largeicons"
+            camera:entries="@array/pref_camera_whitebalance_entries"
+            camera:entryValues="@array/pref_camera_whitebalance_entryvalues"
+            camera:labelList="@array/pref_camera_whitebalance_labels" />
+    <RecordLocationPreference
+            camera:key="pref_camera_recordlocation_key"
+            camera:defaultValue="@string/pref_camera_recordlocation_default"
+            camera:title="@string/pref_camera_recordlocation_title"
+            camera:icons="@array/camera_recordlocation_icons"
+            camera:largeIcons="@array/camera_recordlocation_largeicons"
+            camera:entries="@array/pref_camera_recordlocation_entries"
+            camera:labelList="@array/pref_camera_recordlocation_labels"
+            camera:entryValues="@array/pref_camera_recordlocation_entryvalues" />
+    <ListPreference
+            camera:key="pref_camera_picturesize_key"
+            camera:title="@string/pref_camera_picturesize_title"
+            camera:entries="@array/pref_camera_picturesize_entries"
+            camera:entryValues="@array/pref_camera_picturesize_entryvalues" />
+    <ListPreference
+            camera:key="pref_camera_focusmode_key"
+            camera:defaultValue="@array/pref_camera_focusmode_default_array"
+            camera:title="@string/pref_camera_focusmode_title"
+            camera:entries="@array/pref_camera_focusmode_entries"
+            camera:labelList="@array/pref_camera_focusmode_labels"
+            camera:entryValues="@array/pref_camera_focusmode_entryvalues" />
+    <IconListPreference
+            camera:key="pref_camera_id_key"
+            camera:defaultValue="@string/pref_camera_id_default"
+            camera:title="@string/pref_camera_id_title"
+            camera:icons="@array/camera_id_icons"
+            camera:entries="@array/camera_id_entries"
+            camera:labelList="@array/camera_id_labels"
+            camera:largeIcons="@array/camera_id_largeicons" />
+    <IconListPreference
+            camera:key="pref_camera_hdr_key"
+            camera:defaultValue="@string/pref_camera_hdr_default"
+            camera:title="@string/pref_camera_scenemode_entry_hdr"
+            camera:entries="@array/pref_camera_hdr_entries"
+            camera:icons="@array/pref_camera_hdr_icons"
+            camera:largeIcons="@array/pref_camera_hdr_icons"
+            camera:labelList="@array/pref_camera_hdr_labels"
+            camera:entryValues="@array/pref_camera_hdr_entryvalues" />
+    <CountDownTimerPreference
+            camera:key="pref_camera_timer_key"
+            camera:defaultValue="@string/pref_camera_timer_default"
+            camera:title="@string/pref_camera_timer_title" />
+    <ListPreference
+            camera:key="pref_camera_timer_sound_key"
+            camera:defaultValue="@string/pref_camera_timer_sound_default"
+            camera:title="@string/pref_camera_timer_sound_title"
+            camera:entries="@array/pref_camera_timer_sound_entries"
+            camera:entryValues="@array/pref_camera_timer_sound_entryvalues" />
+</PreferenceGroup>
diff --git a/res/xml/video_preferences.xml b/res/xml/video_preferences.xml
new file mode 100644
index 0000000..faf5f65
--- /dev/null
+++ b/res/xml/video_preferences.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<PreferenceGroup
+        xmlns:camera="http://schemas.android.com/apk/res/com.android.gallery3d"
+        camera:title="@string/pref_camcorder_settings_category">
+    <ListPreference
+            camera:key="pref_video_quality_key"
+            camera:defaultValue="@string/pref_video_quality_default"
+            camera:title="@string/pref_video_quality_title"
+            camera:entries="@array/pref_video_quality_entries"
+            camera:entryValues="@array/pref_video_quality_entryvalues"/>
+    <IconListPreference
+            camera:key="pref_video_time_lapse_frame_interval_key"
+            camera:defaultValue="@string/pref_video_time_lapse_frame_interval_default"
+            camera:title="@string/pref_video_time_lapse_frame_interval_title"
+            camera:singleIcon="@drawable/ic_timelapse_none"
+            camera:entries="@array/pref_video_time_lapse_frame_interval_entries"
+            camera:entryValues="@array/pref_video_time_lapse_frame_interval_entryvalues"/>
+    <IconListPreference
+            camera:key="pref_camera_video_flashmode_key"
+            camera:defaultValue="@string/pref_camera_video_flashmode_default"
+            camera:title="@string/pref_camera_flashmode_title"
+            camera:icons="@array/video_flashmode_icons"
+            camera:largeIcons="@array/video_flashmode_largeicons"
+            camera:entries="@array/pref_camera_video_flashmode_entries"
+            camera:labelList="@array/pref_camera_video_flashmode_labels"
+            camera:entryValues="@array/pref_camera_video_flashmode_entryvalues"/>
+    <IconListPreference
+            camera:key="pref_camera_whitebalance_key"
+            camera:defaultValue="@string/pref_camera_whitebalance_default"
+            camera:title="@string/pref_camera_whitebalance_title"
+            camera:icons="@array/whitebalance_icons"
+            camera:largeIcons="@array/whitebalance_largeicons"
+            camera:entries="@array/pref_camera_whitebalance_entries"
+            camera:labelList="@array/pref_camera_whitebalance_labels"
+            camera:entryValues="@array/pref_camera_whitebalance_entryvalues"/>
+    <IconListPreference
+            camera:key="pref_camera_id_key"
+            camera:defaultValue="@string/pref_camera_id_default"
+            camera:title="@string/pref_camera_id_title"
+            camera:icons="@array/camera_id_icons"
+            camera:entries="@array/camera_id_entries"
+            camera:labelList="@array/camera_id_labels"
+            camera:largeIcons="@array/camera_id_largeicons"/>
+    <IconListPreference
+            camera:key="pref_video_effect_key"
+            camera:defaultValue="@string/pref_video_effect_default"
+            camera:title="@string/pref_video_effect_title"
+            camera:icons="@array/video_effect_icons"
+            camera:largeIcons="@array/video_effect_icons"
+            camera:entries="@array/pref_video_effect_entries"
+            camera:entryValues="@array/pref_video_effect_entryvalues" />
+    <RecordLocationPreference
+            camera:key="pref_camera_recordlocation_key"
+            camera:defaultValue="@string/pref_camera_recordlocation_default"
+            camera:title="@string/pref_camera_recordlocation_title"
+            camera:icons="@array/camera_recordlocation_icons"
+            camera:largeIcons="@array/camera_recordlocation_largeicons"
+            camera:entries="@array/pref_camera_recordlocation_entries"
+            camera:entryValues="@array/pref_camera_recordlocation_entryvalues" />
+</PreferenceGroup>
diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644
index 0000000..40bab1e
--- /dev/null
+++ b/src/android/util/Pools.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/ActivityBase.java b/src/com/android/camera/ActivityBase.java
new file mode 100644
index 0000000..59bd82c
--- /dev/null
+++ b/src/com/android/camera/ActivityBase.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.hardware.Camera.Parameters;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+
+import com.android.camera.ui.LayoutChangeNotifier;
+import com.android.camera.ui.PopupManager;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.AbstractGalleryActivity;
+import com.android.gallery3d.app.AppBridge;
+import com.android.gallery3d.app.FilmstripPage;
+import com.android.gallery3d.app.GalleryActionBar;
+import com.android.gallery3d.app.PhotoPage;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.util.MediaSetUtils;
+
+/**
+ * Superclass of camera activity.
+ */
+public abstract class ActivityBase extends AbstractGalleryActivity
+        implements LayoutChangeNotifier.Listener {
+
+    private static final String TAG = "ActivityBase";
+    private static final int CAMERA_APP_VIEW_TOGGLE_TIME = 100;  // milliseconds
+    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
+            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
+    public static final String ACTION_IMAGE_CAPTURE_SECURE =
+            "android.media.action.IMAGE_CAPTURE_SECURE";
+    // The intent extra for camera from secure lock screen. True if the gallery
+    // should only show newly captured pictures. sSecureAlbumId does not
+    // increment. This is used when switching between camera, camcorder, and
+    // panorama. If the extra is not set, it is in the normal camera mode.
+    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
+
+    private int mResultCodeForTesting;
+    private Intent mResultDataForTesting;
+    private OnScreenHint mStorageHint;
+    private View mSingleTapArea;
+
+    protected boolean mOpenCameraFail;
+    protected boolean mCameraDisabled;
+    protected CameraManager.CameraProxy mCameraDevice;
+    protected Parameters mParameters;
+    // The activity is paused. The classes that extend this class should set
+    // mPaused the first thing in onResume/onPause.
+    protected boolean mPaused;
+    protected GalleryActionBar mActionBar;
+
+    // multiple cameras support
+    protected int mNumberOfCameras;
+    protected int mCameraId;
+    // The activity is going to switch to the specified camera id. This is
+    // needed because texture copy is done in GL thread. -1 means camera is not
+    // switching.
+    protected int mPendingSwitchCameraId = -1;
+
+    protected MyAppBridge mAppBridge;
+    protected ScreenNail mCameraScreenNail; // This shows camera preview.
+    // The view containing only camera related widgets like control panel,
+    // indicator bar, focus indicator and etc.
+    protected View mCameraAppView;
+    protected boolean mShowCameraAppView = true;
+    private Animation mCameraAppViewFadeIn;
+    private Animation mCameraAppViewFadeOut;
+    // Secure album id. This should be incremented every time the camera is
+    // launched from the secure lock screen. The id should be the same when
+    // switching between camera, camcorder, and panorama.
+    protected static int sSecureAlbumId;
+    // True if the camera is started from secure lock screen.
+    protected boolean mSecureCamera;
+    private static boolean sFirstStartAfterScreenOn = true;
+
+    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
+    private static final int UPDATE_STORAGE_HINT = 0;
+    private final Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case UPDATE_STORAGE_HINT:
+                        updateStorageHint();
+                        return;
+                }
+            }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_MEDIA_MOUNTED)
+                    || action.equals(Intent.ACTION_MEDIA_UNMOUNTED)
+                    || action.equals(Intent.ACTION_MEDIA_CHECKING)
+                    || action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
+                updateStorageSpaceAndHint();
+            }
+        }
+    };
+
+    // close activity when screen turns off
+    private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            finish();
+        }
+    };
+
+    private static BroadcastReceiver sScreenOffReceiver;
+    private static class ScreenOffReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            sFirstStartAfterScreenOn = true;
+        }
+    }
+
+    public static boolean isFirstStartAfterScreenOn() {
+        return sFirstStartAfterScreenOn;
+    }
+
+    public static void resetFirstStartAfterScreenOn() {
+        sFirstStartAfterScreenOn = false;
+    }
+
+    protected class CameraOpenThread extends Thread {
+        @Override
+        public void run() {
+            try {
+                mCameraDevice = Util.openCamera(ActivityBase.this, mCameraId);
+                mParameters = mCameraDevice.getParameters();
+            } catch (CameraHardwareException e) {
+                mOpenCameraFail = true;
+            } catch (CameraDisabledException e) {
+                mCameraDisabled = true;
+            }
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.disableToggleStatusBar();
+        // Set a theme with action bar. It is not specified in manifest because
+        // we want to hide it by default. setTheme must happen before
+        // setContentView.
+        //
+        // This must be set before we call super.onCreate(), where the window's
+        // background is removed.
+        setTheme(R.style.Theme_Gallery);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        if (ApiHelper.HAS_ACTION_BAR) {
+            requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
+        } else {
+            requestWindowFeature(Window.FEATURE_NO_TITLE);
+        }
+
+        // Check if this is in the secure camera mode.
+        Intent intent = getIntent();
+        String action = intent.getAction();
+        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
+            mSecureCamera = true;
+            // Use a new album when this is started from the lock screen.
+            sSecureAlbumId++;
+        } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
+            mSecureCamera = true;
+        } else {
+            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
+        }
+        if (mSecureCamera) {
+            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+            registerReceiver(mScreenOffReceiver, filter);
+            if (sScreenOffReceiver == null) {
+                sScreenOffReceiver = new ScreenOffReceiver();
+                getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
+            }
+        }
+        super.onCreate(icicle);
+    }
+
+    public boolean isPanoramaActivity() {
+        return false;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        installIntentFilter();
+        if (updateStorageHintOnResume()) {
+            updateStorageSpace();
+            mHandler.sendEmptyMessageDelayed(UPDATE_STORAGE_HINT, 200);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+        if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+
+        unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void setContentView(int layoutResID) {
+        super.setContentView(layoutResID);
+        // getActionBar() should be after setContentView
+        mActionBar = new GalleryActionBar(this);
+        mActionBar.hide();
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        return false;
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Prevent software keyboard or voice search from showing up.
+        if (keyCode == KeyEvent.KEYCODE_SEARCH
+                || keyCode == KeyEvent.KEYCODE_MENU) {
+            if (event.isLongPress()) return true;
+        }
+        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraAppView) {
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    protected void setResultEx(int resultCode) {
+        mResultCodeForTesting = resultCode;
+        setResult(resultCode);
+    }
+
+    protected void setResultEx(int resultCode, Intent data) {
+        mResultCodeForTesting = resultCode;
+        mResultDataForTesting = data;
+        setResult(resultCode, data);
+    }
+
+    public int getResultCode() {
+        return mResultCodeForTesting;
+    }
+
+    public Intent getResultData() {
+        return mResultDataForTesting;
+    }
+
+    @Override
+    protected void onDestroy() {
+        PopupManager.removeInstance(this);
+        if (mSecureCamera) unregisterReceiver(mScreenOffReceiver);
+        super.onDestroy();
+    }
+
+    protected void installIntentFilter() {
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter =
+                new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
+        intentFilter.addDataScheme("file");
+        registerReceiver(mReceiver, intentFilter);
+    }
+
+    protected void updateStorageSpace() {
+        mStorageSpace = Storage.getAvailableSpace();
+    }
+
+    protected long getStorageSpace() {
+        return mStorageSpace;
+    }
+
+    protected void updateStorageSpaceAndHint() {
+        updateStorageSpace();
+        updateStorageHint(mStorageSpace);
+    }
+
+    protected void updateStorageHint() {
+        updateStorageHint(mStorageSpace);
+    }
+
+    protected boolean updateStorageHintOnResume() {
+        return true;
+    }
+
+    protected void updateStorageHint(long storageSpace) {
+        String message = null;
+        if (storageSpace == Storage.UNAVAILABLE) {
+            message = getString(R.string.no_storage);
+        } else if (storageSpace == Storage.PREPARING) {
+            message = getString(R.string.preparing_sd);
+        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
+            message = getString(R.string.access_sd_fail);
+        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
+            message = getString(R.string.spaceIsLow_content);
+        }
+
+        if (message != null) {
+            if (mStorageHint == null) {
+                mStorageHint = OnScreenHint.makeText(this, message);
+            } else {
+                mStorageHint.setText(message);
+            }
+            mStorageHint.show();
+        } else if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+    }
+
+    public void gotoGallery() {
+        // Move the next picture with capture animation. "1" means next.
+        mAppBridge.switchWithCaptureAnimation(1);
+    }
+
+    // Call this after setContentView.
+    public ScreenNail createCameraScreenNail(boolean getPictures) {
+        mCameraAppView = findViewById(R.id.camera_app_root);
+        Bundle data = new Bundle();
+        String path;
+        if (getPictures) {
+            if (mSecureCamera) {
+                path = "/secure/all/" + sSecureAlbumId;
+            } else {
+                path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
+            }
+        } else {
+            path = "/local/all/0"; // Use 0 so gallery does not show anything.
+        }
+        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
+        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
+        data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
+
+        // Send an AppBridge to gallery to enable the camera preview.
+        if (mAppBridge != null) {
+            mCameraScreenNail.recycle();
+        }
+        mAppBridge = new MyAppBridge();
+        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
+        if (getStateManager().getStateCount() == 0) {
+            getStateManager().startState(FilmstripPage.class, data);
+        } else {
+            getStateManager().switchState(getStateManager().getTopState(),
+                    FilmstripPage.class, data);
+        }
+        mCameraScreenNail = mAppBridge.getCameraScreenNail();
+        return mCameraScreenNail;
+    }
+
+    // Call this after setContentView.
+    protected ScreenNail reuseCameraScreenNail(boolean getPictures) {
+        mCameraAppView = findViewById(R.id.camera_app_root);
+        Bundle data = new Bundle();
+        String path;
+        if (getPictures) {
+            if (mSecureCamera) {
+                path = "/secure/all/" + sSecureAlbumId;
+            } else {
+                path = "/local/all/" + MediaSetUtils.CAMERA_BUCKET_ID;
+            }
+        } else {
+            path = "/local/all/0"; // Use 0 so gallery does not show anything.
+        }
+        data.putString(PhotoPage.KEY_MEDIA_SET_PATH, path);
+        data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path);
+        data.putBoolean(PhotoPage.KEY_SHOW_WHEN_LOCKED, mSecureCamera);
+
+        // Send an AppBridge to gallery to enable the camera preview.
+        if (mAppBridge == null) {
+            mAppBridge = new MyAppBridge();
+        }
+        data.putParcelable(PhotoPage.KEY_APP_BRIDGE, mAppBridge);
+        if (getStateManager().getStateCount() == 0) {
+            getStateManager().startState(FilmstripPage.class, data);
+        }
+        mCameraScreenNail = mAppBridge.getCameraScreenNail();
+        return mCameraScreenNail;
+    }
+
+    private class HideCameraAppView implements Animation.AnimationListener {
+        @Override
+        public void onAnimationEnd(Animation animation) {
+            // We cannot set this as GONE because we want to receive the
+            // onLayoutChange() callback even when we are invisible.
+            mCameraAppView.setVisibility(View.INVISIBLE);
+        }
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {
+        }
+
+        @Override
+        public void onAnimationStart(Animation animation) {
+        }
+    }
+
+    protected void updateCameraAppView() {
+        // Initialize the animation.
+        if (mCameraAppViewFadeIn == null) {
+            mCameraAppViewFadeIn = new AlphaAnimation(0f, 1f);
+            mCameraAppViewFadeIn.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
+            mCameraAppViewFadeIn.setInterpolator(new DecelerateInterpolator());
+
+            mCameraAppViewFadeOut = new AlphaAnimation(1f, 0f);
+            mCameraAppViewFadeOut.setDuration(CAMERA_APP_VIEW_TOGGLE_TIME);
+            mCameraAppViewFadeOut.setInterpolator(new DecelerateInterpolator());
+            mCameraAppViewFadeOut.setAnimationListener(new HideCameraAppView());
+        }
+
+        if (mShowCameraAppView) {
+            mCameraAppView.setVisibility(View.VISIBLE);
+            // The "transparent region" is not recomputed when a sibling of
+            // SurfaceView changes visibility (unless it involves GONE). It's
+            // been broken since 1.0. Call requestLayout to work around it.
+            mCameraAppView.requestLayout();
+            mCameraAppView.startAnimation(mCameraAppViewFadeIn);
+        } else {
+            mCameraAppView.startAnimation(mCameraAppViewFadeOut);
+        }
+    }
+
+    protected void onFullScreenChanged(boolean full) {
+        if (mShowCameraAppView == full) return;
+        mShowCameraAppView = full;
+        if (mPaused || isFinishing()) return;
+        updateCameraAppView();
+    }
+
+    @Override
+    public GalleryActionBar getGalleryActionBar() {
+        return mActionBar;
+    }
+
+    // Preview frame layout has changed.
+    @Override
+    public void onLayoutChange(View v, int left, int top, int right, int bottom) {
+        if (mAppBridge == null) return;
+
+        int width = right - left;
+        int height = bottom - top;
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            CameraScreenNail screenNail = (CameraScreenNail) mCameraScreenNail;
+            if (Util.getDisplayRotation(this) % 180 == 0) {
+                screenNail.setPreviewFrameLayoutSize(width, height);
+            } else {
+                // Swap the width and height. Camera screen nail draw() is based on
+                // natural orientation, not the view system orientation.
+                screenNail.setPreviewFrameLayoutSize(height, width);
+            }
+            notifyScreenNailChanged();
+        }
+    }
+
+    protected void setSingleTapUpListener(View singleTapArea) {
+        mSingleTapArea = singleTapArea;
+    }
+
+    private boolean onSingleTapUp(int x, int y) {
+        // Ignore if listener is null or the camera control is invisible.
+        if (mSingleTapArea == null || !mShowCameraAppView) return false;
+
+        int[] relativeLocation = Util.getRelativeLocation((View) getGLRoot(),
+                mSingleTapArea);
+        x -= relativeLocation[0];
+        y -= relativeLocation[1];
+        if (x >= 0 && x < mSingleTapArea.getWidth() && y >= 0
+                && y < mSingleTapArea.getHeight()) {
+            onSingleTapUp(mSingleTapArea, x, y);
+            return true;
+        }
+        return false;
+    }
+
+    protected void onSingleTapUp(View view, int x, int y) {
+    }
+
+    public void setSwipingEnabled(boolean enabled) {
+        mAppBridge.setSwipingEnabled(enabled);
+    }
+
+    public void notifyScreenNailChanged() {
+        mAppBridge.notifyScreenNailChanged();
+    }
+
+    protected void onPreviewTextureCopied() {
+    }
+
+    protected void onCaptureTextureCopied() {
+    }
+
+    protected void addSecureAlbumItemIfNeeded(boolean isVideo, Uri uri) {
+        if (mSecureCamera) {
+            int id = Integer.parseInt(uri.getLastPathSegment());
+            mAppBridge.addSecureAlbumItem(isVideo, id);
+        }
+    }
+
+    public boolean isSecureCamera() {
+        return mSecureCamera;
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    //  The is the communication interface between the Camera Application and
+    //  the Gallery PhotoPage.
+    //////////////////////////////////////////////////////////////////////////
+
+    class MyAppBridge extends AppBridge implements CameraScreenNail.Listener {
+        @SuppressWarnings("hiding")
+        private ScreenNail mCameraScreenNail;
+        private Server mServer;
+
+        @Override
+        public ScreenNail attachScreenNail() {
+            if (mCameraScreenNail == null) {
+                if (ApiHelper.HAS_SURFACE_TEXTURE) {
+                    mCameraScreenNail = new CameraScreenNail(this, ActivityBase.this);
+                } else {
+                    Bitmap b = BitmapFactory.decodeResource(getResources(),
+                            R.drawable.wallpaper_picker_preview);
+                    mCameraScreenNail = new StaticBitmapScreenNail(b);
+                }
+            }
+            return mCameraScreenNail;
+        }
+
+        @Override
+        public void detachScreenNail() {
+            mCameraScreenNail = null;
+        }
+
+        public ScreenNail getCameraScreenNail() {
+            return mCameraScreenNail;
+        }
+
+        // Return true if the tap is consumed.
+        @Override
+        public boolean onSingleTapUp(int x, int y) {
+            return ActivityBase.this.onSingleTapUp(x, y);
+        }
+
+        // This is used to notify that the screen nail will be drawn in full screen
+        // or not in next draw() call.
+        @Override
+        public void onFullScreenChanged(boolean full) {
+            ActivityBase.this.onFullScreenChanged(full);
+        }
+
+        @Override
+        public void requestRender() {
+            getGLRoot().requestRenderForced();
+        }
+
+        @Override
+        public void onPreviewTextureCopied() {
+            ActivityBase.this.onPreviewTextureCopied();
+        }
+
+        @Override
+        public void onCaptureTextureCopied() {
+            ActivityBase.this.onCaptureTextureCopied();
+        }
+
+        @Override
+        public void setServer(Server s) {
+            mServer = s;
+        }
+
+        @Override
+        public boolean isPanorama() {
+            return ActivityBase.this.isPanoramaActivity();
+        }
+
+        @Override
+        public boolean isStaticCamera() {
+            return !ApiHelper.HAS_SURFACE_TEXTURE;
+        }
+
+        public void addSecureAlbumItem(boolean isVideo, int id) {
+            if (mServer != null) mServer.addSecureAlbumItem(isVideo, id);
+        }
+
+        private void setCameraRelativeFrame(Rect frame) {
+            if (mServer != null) mServer.setCameraRelativeFrame(frame);
+        }
+
+        private void switchWithCaptureAnimation(int offset) {
+            if (mServer != null) mServer.switchWithCaptureAnimation(offset);
+        }
+
+        private void setSwipingEnabled(boolean enabled) {
+            if (mServer != null) mServer.setSwipingEnabled(enabled);
+        }
+
+        private void notifyScreenNailChanged() {
+            if (mServer != null) mServer.notifyScreenNailChanged();
+        }
+    }
+}
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
new file mode 100644
index 0000000..5ba769a
--- /dev/null
+++ b/src/com/android/camera/CameraActivity.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2012 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+import com.android.camera.ui.CameraSwitcher;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.PhotoPage;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.LightCycleHelper;
+
+public class CameraActivity extends ActivityBase
+        implements CameraSwitcher.CameraSwitchListener {
+    public static final int PHOTO_MODULE_INDEX = 0;
+    public static final int VIDEO_MODULE_INDEX = 1;
+    public static final int PANORAMA_MODULE_INDEX = 2;
+    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
+
+    CameraModule mCurrentModule;
+    private FrameLayout mFrame;
+    private ShutterButton mShutter;
+    private CameraSwitcher mSwitcher;
+    private View mCameraControls;
+    private View mControlsBackground;
+    private View mPieMenuButton;
+    private Drawable[] mDrawables;
+    private int mCurrentModuleIndex;
+    private MotionEvent mDown;
+    private boolean mAutoRotateScreen;
+    private int mHeightOrWidth = -1;
+
+    private MyOrientationEventListener mOrientationListener;
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mLastRawOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+    private MediaSaveService mMediaSaveService;
+    private ServiceConnection mConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder b) {
+                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
+                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                mMediaSaveService = null;
+            }};
+
+    private static final String TAG = "CAM_activity";
+
+    private static final int[] DRAW_IDS = {
+            R.drawable.ic_switch_camera,
+            R.drawable.ic_switch_video,
+            R.drawable.ic_switch_pan,
+            R.drawable.ic_switch_photosphere
+    };
+
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+        setContentView(R.layout.camera_main);
+        mFrame = (FrameLayout) findViewById(R.id.camera_app_root);
+        mDrawables = new Drawable[DRAW_IDS.length];
+        for (int i = 0; i < DRAW_IDS.length; i++) {
+            mDrawables[i] = getResources().getDrawable(DRAW_IDS[i]);
+        }
+        init();
+        if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction())
+                || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) {
+            mCurrentModule = new VideoModule();
+            mCurrentModuleIndex = VIDEO_MODULE_INDEX;
+        } else {
+            mCurrentModule = new PhotoModule();
+            mCurrentModuleIndex = PHOTO_MODULE_INDEX;
+        }
+        mCurrentModule.init(this, mFrame, true);
+        mSwitcher.setCurrentIndex(mCurrentModuleIndex);
+        mOrientationListener = new MyOrientationEventListener(this);
+        bindMediaSaveService();
+    }
+
+    public void init() {
+        boolean landscape = Util.getDisplayRotation(this) % 180 == 90;
+        mControlsBackground = findViewById(R.id.blocker);
+        mCameraControls = findViewById(R.id.camera_controls);
+        mShutter = (ShutterButton) findViewById(R.id.shutter_button);
+        mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher);
+        mPieMenuButton = findViewById(R.id.menu);
+        int totaldrawid = (LightCycleHelper.hasLightCycleCapture(this)
+                                ? DRAW_IDS.length : DRAW_IDS.length - 1);
+        if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
+
+        int[] drawids = new int[totaldrawid];
+        int[] moduleids = new int[totaldrawid];
+        int ix = 0;
+        for (int i = 0; i < mDrawables.length; i++) {
+            if (i == PANORAMA_MODULE_INDEX && !ApiHelper.HAS_OLD_PANORAMA) {
+                continue; // not enabled, so don't add to UI
+            }
+            if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(this)) {
+                continue; // not enabled, so don't add to UI
+            }
+            moduleids[ix] = i;
+            drawids[ix++] = DRAW_IDS[i];
+        }
+        mSwitcher.setIds(moduleids, drawids);
+        mSwitcher.setSwitchListener(this);
+        mSwitcher.setCurrentIndex(mCurrentModuleIndex);
+    }
+
+    @Override
+    public void onDestroy() {
+        unbindMediaSaveService();
+        super.onDestroy();
+    }
+
+    // Return whether the auto-rotate screen in system settings
+    // is turned on.
+    public boolean isAutoRotateScreen() {
+        return mAutoRotateScreen;
+    }
+
+    private class MyOrientationEventListener
+            extends OrientationEventListener {
+        public MyOrientationEventListener(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onOrientationChanged(int orientation) {
+            // We keep the last known orientation. So if the user first orient
+            // the camera then point the camera to floor or sky, we still have
+            // the correct orientation.
+            if (orientation == ORIENTATION_UNKNOWN) return;
+            mLastRawOrientation = orientation;
+            mCurrentModule.onOrientationChanged(orientation);
+        }
+    }
+
+    private ObjectAnimator mCameraSwitchAnimator;
+
+    @Override
+    public void onCameraSelected(final int i) {
+        if (mPaused) return;
+        if (i != mCurrentModuleIndex) {
+            mPaused = true;
+            CameraScreenNail screenNail = getCameraScreenNail();
+            if (screenNail != null) {
+                if (mCameraSwitchAnimator != null && mCameraSwitchAnimator.isRunning()) {
+                    mCameraSwitchAnimator.cancel();
+                }
+                mCameraSwitchAnimator = ObjectAnimator.ofFloat(
+                        screenNail, "alpha", screenNail.getAlpha(), 0f);
+                mCameraSwitchAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        doChangeCamera(i);
+                    }
+                });
+                mCameraSwitchAnimator.start();
+            } else {
+                doChangeCamera(i);
+            }
+        }
+    }
+
+    private void doChangeCamera(int i) {
+        boolean canReuse = canReuseScreenNail();
+        CameraHolder.instance().keep();
+        closeModule(mCurrentModule);
+        mCurrentModuleIndex = i;
+        switch (i) {
+            case VIDEO_MODULE_INDEX:
+                mCurrentModule = new VideoModule();
+                break;
+            case PHOTO_MODULE_INDEX:
+                mCurrentModule = new PhotoModule();
+                break;
+            case PANORAMA_MODULE_INDEX:
+                mCurrentModule = new PanoramaModule();
+                break;
+            case LIGHTCYCLE_MODULE_INDEX:
+                mCurrentModule = LightCycleHelper.createPanoramaModule();
+                break;
+        }
+        showPieMenuButton(mCurrentModule.needsPieMenu());
+
+        openModule(mCurrentModule, canReuse);
+        mCurrentModule.onOrientationChanged(mLastRawOrientation);
+        if (mMediaSaveService != null) {
+            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+        }
+        getCameraScreenNail().setAlpha(0f);
+        getCameraScreenNail().setOnFrameDrawnOneShot(mOnFrameDrawn);
+    }
+
+    public void showPieMenuButton(boolean show) {
+        if (show) {
+            findViewById(R.id.blocker).setVisibility(View.VISIBLE);
+            findViewById(R.id.menu).setVisibility(View.VISIBLE);
+            findViewById(R.id.on_screen_indicators).setVisibility(View.VISIBLE);
+        } else {
+            findViewById(R.id.blocker).setVisibility(View.INVISIBLE);
+            findViewById(R.id.menu).setVisibility(View.INVISIBLE);
+            findViewById(R.id.on_screen_indicators).setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private Runnable mOnFrameDrawn = new Runnable() {
+
+        @Override
+        public void run() {
+            runOnUiThread(mFadeInCameraScreenNail);
+        }
+    };
+
+    private Runnable mFadeInCameraScreenNail = new Runnable() {
+
+        @Override
+        public void run() {
+            mCameraSwitchAnimator = ObjectAnimator.ofFloat(
+                    getCameraScreenNail(), "alpha", 0f, 1f);
+            mCameraSwitchAnimator.setStartDelay(50);
+            mCameraSwitchAnimator.start();
+        }
+    };
+
+    @Override
+    public void onShowSwitcherPopup() {
+        mCurrentModule.onShowSwitcherPopup();
+    }
+
+    private void openModule(CameraModule module, boolean canReuse) {
+        module.init(this, mFrame, canReuse && canReuseScreenNail());
+        mPaused = false;
+        module.onResumeBeforeSuper();
+        module.onResumeAfterSuper();
+    }
+
+    private void closeModule(CameraModule module) {
+        module.onPauseBeforeSuper();
+        module.onPauseAfterSuper();
+        mFrame.removeAllViews();
+    }
+
+    public ShutterButton getShutterButton() {
+        return mShutter;
+    }
+
+    public void hideUI() {
+        mCameraControls.setVisibility(View.INVISIBLE);
+        hideSwitcher();
+        mShutter.setVisibility(View.GONE);
+    }
+
+    public void showUI() {
+        mCameraControls.setVisibility(View.VISIBLE);
+        showSwitcher();
+        mShutter.setVisibility(View.VISIBLE);
+        // Force a layout change to show shutter button
+        mShutter.requestLayout();
+    }
+
+    public void hideSwitcher() {
+        mSwitcher.closePopup();
+        mSwitcher.setVisibility(View.INVISIBLE);
+    }
+
+    public void showSwitcher() {
+        if (mCurrentModule.needsSwitcher()) {
+            mSwitcher.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public boolean isInCameraApp() {
+        return mShowCameraAppView;
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+        mCurrentModule.onConfigurationChanged(config);
+    }
+
+    @Override
+    public void onPause() {
+        mPaused = true;
+        mOrientationListener.disable();
+        mCurrentModule.onPauseBeforeSuper();
+        super.onPause();
+        mCurrentModule.onPauseAfterSuper();
+    }
+
+    @Override
+    public void onResume() {
+        mPaused = false;
+        if (Settings.System.getInt(getContentResolver(),
+                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            mAutoRotateScreen = false;
+        } else {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+            mAutoRotateScreen = true;
+        }
+        mOrientationListener.enable();
+        mCurrentModule.onResumeBeforeSuper();
+        super.onResume();
+        mCurrentModule.onResumeAfterSuper();
+    }
+
+    private void bindMediaSaveService() {
+        Intent intent = new Intent(this, MediaSaveService.class);
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private void unbindMediaSaveService() {
+        if (mMediaSaveService != null) {
+            mMediaSaveService.setListener(null);
+        }
+        if (mConnection != null) {
+            unbindService(mConnection);
+        }
+    }
+
+    @Override
+    protected void onFullScreenChanged(boolean full) {
+        if (full) {
+            showUI();
+        } else {
+            hideUI();
+        }
+        super.onFullScreenChanged(full);
+        if (ApiHelper.HAS_ROTATION_ANIMATION) {
+            setRotationAnimation(full);
+        }
+        mCurrentModule.onFullScreenChanged(full);
+    }
+
+    private void setRotationAnimation(boolean fullscreen) {
+        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+        if (fullscreen) {
+            rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+        }
+        Window win = getWindow();
+        WindowManager.LayoutParams winParams = win.getAttributes();
+        winParams.rotationAnimation = rotationAnimation;
+        win.setAttributes(winParams);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mCurrentModule.onStop();
+        getStateManager().clearTasks();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        getStateManager().clearActivityResult();
+    }
+
+    @Override
+    protected void installIntentFilter() {
+        super.installIntentFilter();
+        mCurrentModule.installIntentFilter();
+    }
+
+    @Override
+    protected void onActivityResult(
+            int requestCode, int resultCode, Intent data) {
+        // Only PhotoPage understands ProxyLauncher.RESULT_USER_CANCELED
+        if (resultCode == ProxyLauncher.RESULT_USER_CANCELED
+                && !(getStateManager().getTopState() instanceof PhotoPage)) {
+            resultCode = RESULT_CANCELED;
+        }
+        super.onActivityResult(requestCode, resultCode, data);
+        // Unmap cancel vs. reset
+        if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) {
+            resultCode = RESULT_CANCELED;
+        }
+        mCurrentModule.onActivityResult(requestCode, resultCode, data);
+    }
+
+    // Preview area is touched. Handle touch focus.
+    // Touch to focus is handled by PreviewGestures, this function call
+    // is no longer needed. TODO: Clean it up in the next refactor
+    @Override
+    protected void onSingleTapUp(View view, int x, int y) {
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (!mCurrentModule.onBackPressed()) {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mCurrentModule.onKeyDown(keyCode,  event)
+                || super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return mCurrentModule.onKeyUp(keyCode,  event)
+                || super.onKeyUp(keyCode, event);
+    }
+
+    public void cancelActivityTouchHandling() {
+        if (mDown != null) {
+            MotionEvent cancel = MotionEvent.obtain(mDown);
+            cancel.setAction(MotionEvent.ACTION_CANCEL);
+            super.dispatchTouchEvent(cancel);
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (m.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mDown = m;
+        }
+        if ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) {
+            return mSwitcher.onTouch(null, m);
+        } else if ((mSwitcher != null) && mSwitcher.isInsidePopup(m)) {
+            return superDispatchTouchEvent(m);
+        } else {
+            return mCurrentModule.dispatchTouchEvent(m);
+        }
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        Intent proxyIntent = new Intent(this, ProxyLauncher.class);
+        proxyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+        proxyIntent.putExtra(Intent.EXTRA_INTENT, intent);
+        super.startActivityForResult(proxyIntent, requestCode);
+    }
+
+    public boolean superDispatchTouchEvent(MotionEvent m) {
+        return super.dispatchTouchEvent(m);
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mCurrentModule.onPreviewTextureCopied();
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+        mCurrentModule.onCaptureTextureCopied();
+    }
+
+    @Override
+    public void onUserInteraction() {
+        super.onUserInteraction();
+        mCurrentModule.onUserInteraction();
+    }
+
+    @Override
+    protected boolean updateStorageHintOnResume() {
+        return mCurrentModule.updateStorageHintOnResume();
+    }
+
+    @Override
+    public void updateCameraAppView() {
+        super.updateCameraAppView();
+        mCurrentModule.updateCameraAppView();
+    }
+
+    private boolean canReuseScreenNail() {
+        return mCurrentModuleIndex == PHOTO_MODULE_INDEX
+                || mCurrentModuleIndex == VIDEO_MODULE_INDEX
+                || mCurrentModuleIndex == LIGHTCYCLE_MODULE_INDEX;
+    }
+
+    @Override
+    public boolean isPanoramaActivity() {
+        return (mCurrentModuleIndex == PANORAMA_MODULE_INDEX);
+    }
+
+    // Accessor methods for getting latency times used in performance testing
+    public long getAutoFocusTime() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mAutoFocusTime : -1;
+    }
+
+    public long getShutterLag() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mShutterLag : -1;
+    }
+
+    public long getShutterToPictureDisplayedTime() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1;
+    }
+
+    public long getPictureDisplayedToJpegCallbackTime() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1;
+    }
+
+    public long getJpegCallbackFinishTime() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1;
+    }
+
+    public long getCaptureStartTime() {
+        return (mCurrentModule instanceof PhotoModule) ?
+                ((PhotoModule) mCurrentModule).mCaptureStartTime : -1;
+    }
+
+    public boolean isRecording() {
+        return (mCurrentModule instanceof VideoModule) ?
+                ((VideoModule) mCurrentModule).isRecording() : false;
+    }
+
+    public CameraScreenNail getCameraScreenNail() {
+        return (CameraScreenNail) mCameraScreenNail;
+    }
+
+    public MediaSaveService getMediaSaveService() {
+        return mMediaSaveService;
+    }
+}
diff --git a/src/com/android/camera/CameraBackupAgent.java b/src/com/android/camera/CameraBackupAgent.java
new file mode 100644
index 0000000..30ba212
--- /dev/null
+++ b/src/com/android/camera/CameraBackupAgent.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.app.backup.BackupAgentHelper;
+import android.app.backup.SharedPreferencesBackupHelper;
+import android.content.Context;
+
+public class CameraBackupAgent extends BackupAgentHelper {
+    private static final String CAMERA_BACKUP_KEY = "camera_prefs";
+
+    public void onCreate () {
+        Context context = getApplicationContext();
+        String prefNames[] = ComboPreferences.getSharedPreferencesNames(context);
+
+        addHelper(CAMERA_BACKUP_KEY, new SharedPreferencesBackupHelper(context, prefNames));
+    }
+}
diff --git a/src/com/android/camera/CameraButtonIntentReceiver.java b/src/com/android/camera/CameraButtonIntentReceiver.java
new file mode 100644
index 0000000..a65942d
--- /dev/null
+++ b/src/com/android/camera/CameraButtonIntentReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * {@code CameraButtonIntentReceiver} is invoked when the camera button is
+ * long-pressed.
+ *
+ * It is declared in {@code AndroidManifest.xml} to receive the
+ * {@code android.intent.action.CAMERA_BUTTON} intent.
+ *
+ * After making sure we can use the camera hardware, it starts the Camera
+ * activity.
+ */
+public class CameraButtonIntentReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Try to get the camera hardware
+        CameraHolder holder = CameraHolder.instance();
+        ComboPreferences pref = new ComboPreferences(context);
+        int cameraId = CameraSettings.readPreferredCameraId(pref);
+        if (holder.tryOpen(cameraId) == null) return;
+
+        // We are going to launch the camera, so hold the camera for later use
+        holder.keep();
+        holder.release();
+        Intent i = new Intent(Intent.ACTION_MAIN);
+        i.setClass(context, CameraActivity.class);
+        i.addCategory(Intent.CATEGORY_LAUNCHER);
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        context.startActivity(i);
+    }
+}
diff --git a/src/com/android/camera/CameraDisabledException.java b/src/com/android/camera/CameraDisabledException.java
new file mode 100644
index 0000000..512809b
--- /dev/null
+++ b/src/com/android/camera/CameraDisabledException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * This class represents the condition that device policy manager has disabled
+ * the camera.
+ */
+public class CameraDisabledException extends Exception {
+}
diff --git a/src/com/android/camera/CameraErrorCallback.java b/src/com/android/camera/CameraErrorCallback.java
new file mode 100644
index 0000000..22f800e
--- /dev/null
+++ b/src/com/android/camera/CameraErrorCallback.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 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.util.Log;
+
+public class CameraErrorCallback
+        implements android.hardware.Camera.ErrorCallback {
+    private static final String TAG = "CameraErrorCallback";
+
+    @Override
+    public void onError(int error, android.hardware.Camera camera) {
+        Log.e(TAG, "Got camera error callback. error=" + error);
+        if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
+            // We are not sure about the current state of the app (in preview or
+            // snapshot or recording). Closing the app is better than creating a
+            // new Camera object.
+            throw new RuntimeException("Media server died.");
+        }
+    }
+}
diff --git a/src/com/android/camera/CameraHardwareException.java b/src/com/android/camera/CameraHardwareException.java
new file mode 100644
index 0000000..8209055
--- /dev/null
+++ b/src/com/android/camera/CameraHardwareException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+/**
+ * This class represents the condition that we cannot open the camera hardware
+ * successfully. For example, another process is using the camera.
+ */
+public class CameraHardwareException extends Exception {
+
+    public CameraHardwareException(Throwable t) {
+        super(t);
+    }
+}
diff --git a/src/com/android/camera/CameraHolder.java b/src/com/android/camera/CameraHolder.java
new file mode 100644
index 0000000..5b7bbfd
--- /dev/null
+++ b/src/com/android/camera/CameraHolder.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import static com.android.camera.Util.Assert;
+
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.camera.CameraManager.CameraProxy;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+
+/**
+ * The class is used to hold an {@code android.hardware.Camera} instance.
+ *
+ * <p>The {@code open()} and {@code release()} calls are similar to the ones
+ * in {@code android.hardware.Camera}. The difference is if {@code keep()} is
+ * called before {@code release()}, CameraHolder will try to hold the {@code
+ * android.hardware.Camera} instance for a while, so if {@code open()} is
+ * called soon after, we can avoid the cost of {@code open()} in {@code
+ * android.hardware.Camera}.
+ *
+ * <p>This is used in switching between different modules.
+ */
+public class CameraHolder {
+    private static final String TAG = "CameraHolder";
+    private static final int KEEP_CAMERA_TIMEOUT = 3000; // 3 seconds
+    private CameraProxy mCameraDevice;
+    private long mKeepBeforeTime;  // Keep the Camera before this time.
+    private final Handler mHandler;
+    private boolean mCameraOpened;  // true if camera is opened
+    private final int mNumberOfCameras;
+    private int mCameraId = -1;  // current camera id
+    private int mBackCameraId = -1;
+    private int mFrontCameraId = -1;
+    private final CameraInfo[] mInfo;
+    private static CameraProxy mMockCamera[];
+    private static CameraInfo mMockCameraInfo[];
+
+    /* Debug double-open issue */
+    private static final boolean DEBUG_OPEN_RELEASE = true;
+    private static class OpenReleaseState {
+        long time;
+        int id;
+        String device;
+        String[] stack;
+    }
+    private static ArrayList<OpenReleaseState> sOpenReleaseStates =
+            new ArrayList<OpenReleaseState>();
+    private static SimpleDateFormat sDateFormat = new SimpleDateFormat(
+            "yyyy-MM-dd HH:mm:ss.SSS");
+
+    private static synchronized void collectState(int id, CameraProxy device) {
+        OpenReleaseState s = new OpenReleaseState();
+        s.time = System.currentTimeMillis();
+        s.id = id;
+        if (device == null) {
+            s.device = "(null)";
+        } else {
+            s.device = device.toString();
+        }
+
+        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
+        String[] lines = new String[stack.length];
+        for (int i = 0; i < stack.length; i++) {
+            lines[i] = stack[i].toString();
+        }
+        s.stack = lines;
+
+        if (sOpenReleaseStates.size() > 10) {
+            sOpenReleaseStates.remove(0);
+        }
+        sOpenReleaseStates.add(s);
+    }
+
+    private static synchronized void dumpStates() {
+        for (int i = sOpenReleaseStates.size() - 1; i >= 0; i--) {
+            OpenReleaseState s = sOpenReleaseStates.get(i);
+            String date = sDateFormat.format(new Date(s.time));
+            Log.d(TAG, "State " + i + " at " + date);
+            Log.d(TAG, "mCameraId = " + s.id + ", mCameraDevice = " + s.device);
+            Log.d(TAG, "Stack:");
+            for (int j = 0; j < s.stack.length; j++) {
+                Log.d(TAG, "  " + s.stack[j]);
+            }
+        }
+    }
+
+    // We store the camera parameters when we actually open the device,
+    // so we can restore them in the subsequent open() requests by the user.
+    // This prevents the parameters set by PhotoModule used by VideoModule
+    // inadvertently.
+    private Parameters mParameters;
+
+    // Use a singleton.
+    private static CameraHolder sHolder;
+    public static synchronized CameraHolder instance() {
+        if (sHolder == null) {
+            sHolder = new CameraHolder();
+        }
+        return sHolder;
+    }
+
+    private static final int RELEASE_CAMERA = 1;
+    private class MyHandler extends Handler {
+        MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case RELEASE_CAMERA:
+                    synchronized (CameraHolder.this) {
+                        // In 'CameraHolder.open', the 'RELEASE_CAMERA' message
+                        // will be removed if it is found in the queue. However,
+                        // there is a chance that this message has been handled
+                        // before being removed. So, we need to add a check
+                        // here:
+                        if (!mCameraOpened) release();
+                    }
+                    break;
+            }
+        }
+    }
+
+    public static void injectMockCamera(CameraInfo[] info, CameraProxy[] camera) {
+        mMockCameraInfo = info;
+        mMockCamera = camera;
+        sHolder = new CameraHolder();
+    }
+
+    private CameraHolder() {
+        HandlerThread ht = new HandlerThread("CameraHolder");
+        ht.start();
+        mHandler = new MyHandler(ht.getLooper());
+        if (mMockCameraInfo != null) {
+            mNumberOfCameras = mMockCameraInfo.length;
+            mInfo = mMockCameraInfo;
+        } else {
+            mNumberOfCameras = android.hardware.Camera.getNumberOfCameras();
+            mInfo = new CameraInfo[mNumberOfCameras];
+            for (int i = 0; i < mNumberOfCameras; i++) {
+                mInfo[i] = new CameraInfo();
+                android.hardware.Camera.getCameraInfo(i, mInfo[i]);
+            }
+        }
+
+        // get the first (smallest) back and first front camera id
+        for (int i = 0; i < mNumberOfCameras; i++) {
+            if (mBackCameraId == -1 && mInfo[i].facing == CameraInfo.CAMERA_FACING_BACK) {
+                mBackCameraId = i;
+            } else if (mFrontCameraId == -1 && mInfo[i].facing == CameraInfo.CAMERA_FACING_FRONT) {
+                mFrontCameraId = i;
+            }
+        }
+    }
+
+    public int getNumberOfCameras() {
+        return mNumberOfCameras;
+    }
+
+    public CameraInfo[] getCameraInfo() {
+        return mInfo;
+    }
+
+    public synchronized CameraProxy open(int cameraId)
+            throws CameraHardwareException {
+        if (DEBUG_OPEN_RELEASE) {
+            collectState(cameraId, mCameraDevice);
+            if (mCameraOpened) {
+                Log.e(TAG, "double open");
+                dumpStates();
+            }
+        }
+        Assert(!mCameraOpened);
+        if (mCameraDevice != null && mCameraId != cameraId) {
+            mCameraDevice.release();
+            mCameraDevice = null;
+            mCameraId = -1;
+        }
+        if (mCameraDevice == null) {
+            try {
+                Log.v(TAG, "open camera " + cameraId);
+                if (mMockCameraInfo == null) {
+                    mCameraDevice = CameraManager.instance().cameraOpen(cameraId);
+                } else {
+                    if (mMockCamera == null)
+                        throw new RuntimeException();
+                    mCameraDevice = mMockCamera[cameraId];
+                }
+                mCameraId = cameraId;
+            } catch (RuntimeException e) {
+                Log.e(TAG, "fail to connect Camera", e);
+                throw new CameraHardwareException(e);
+            }
+            mParameters = mCameraDevice.getParameters();
+        } else {
+            try {
+                mCameraDevice.reconnect();
+            } catch (IOException e) {
+                Log.e(TAG, "reconnect failed.");
+                throw new CameraHardwareException(e);
+            }
+            mCameraDevice.setParameters(mParameters);
+        }
+        mCameraOpened = true;
+        mHandler.removeMessages(RELEASE_CAMERA);
+        mKeepBeforeTime = 0;
+        return mCameraDevice;
+    }
+
+    /**
+     * Tries to open the hardware camera. If the camera is being used or
+     * unavailable then return {@code null}.
+     */
+    public synchronized CameraProxy tryOpen(int cameraId) {
+        try {
+            return !mCameraOpened ? open(cameraId) : null;
+        } catch (CameraHardwareException e) {
+            // In eng build, we throw the exception so that test tool
+            // can detect it and report it
+            if ("eng".equals(Build.TYPE)) {
+                throw new RuntimeException(e);
+            }
+            return null;
+        }
+    }
+
+    public synchronized void release() {
+        if (DEBUG_OPEN_RELEASE) {
+            collectState(mCameraId, mCameraDevice);
+        }
+
+        if (mCameraDevice == null) return;
+
+        long now = System.currentTimeMillis();
+        if (now < mKeepBeforeTime) {
+            if (mCameraOpened) {
+                mCameraOpened = false;
+                mCameraDevice.stopPreview();
+            }
+            mHandler.sendEmptyMessageDelayed(RELEASE_CAMERA,
+                    mKeepBeforeTime - now);
+            return;
+        }
+        mCameraOpened = false;
+        mCameraDevice.release();
+        mCameraDevice = null;
+        // We must set this to null because it has a reference to Camera.
+        // Camera has references to the listeners.
+        mParameters = null;
+        mCameraId = -1;
+    }
+
+    public void keep() {
+        keep(KEEP_CAMERA_TIMEOUT);
+    }
+
+    public synchronized void keep(int time) {
+        // We allow mCameraOpened in either state for the convenience of the
+        // calling activity. The activity may not have a chance to call open()
+        // before the user switches to another activity.
+        mKeepBeforeTime = System.currentTimeMillis() + time;
+    }
+
+    public int getBackCameraId() {
+        return mBackCameraId;
+    }
+
+    public int getFrontCameraId() {
+        return mFrontCameraId;
+    }
+}
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
new file mode 100644
index 0000000..c7005cf5
--- /dev/null
+++ b/src/com/android/camera/CameraManager.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2012 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 static com.android.camera.Util.Assert;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.AutoFocusCallback;
+import android.hardware.Camera.AutoFocusMoveCallback;
+import android.hardware.Camera.ErrorCallback;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.Camera.OnZoomChangeListener;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.PreviewCallback;
+import android.hardware.Camera.ShutterCallback;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.android.gallery3d.common.ApiHelper;
+
+import java.io.IOException;
+
+public class CameraManager {
+    private static final String TAG = "CameraManager";
+    private static CameraManager sCameraManager = new CameraManager();
+
+    private Parameters mParameters;
+    private boolean mParametersIsDirty;
+    private IOException mReconnectIOException;
+
+    private static final int RELEASE = 1;
+    private static final int RECONNECT = 2;
+    private static final int UNLOCK = 3;
+    private static final int LOCK = 4;
+    private static final int SET_PREVIEW_TEXTURE_ASYNC = 5;
+    private static final int START_PREVIEW_ASYNC = 6;
+    private static final int STOP_PREVIEW = 7;
+    private static final int SET_PREVIEW_CALLBACK_WITH_BUFFER = 8;
+    private static final int ADD_CALLBACK_BUFFER = 9;
+    private static final int AUTO_FOCUS = 10;
+    private static final int CANCEL_AUTO_FOCUS = 11;
+    private static final int SET_AUTO_FOCUS_MOVE_CALLBACK = 12;
+    private static final int SET_DISPLAY_ORIENTATION = 13;
+    private static final int SET_ZOOM_CHANGE_LISTENER = 14;
+    private static final int SET_FACE_DETECTION_LISTENER = 15;
+    private static final int START_FACE_DETECTION = 16;
+    private static final int STOP_FACE_DETECTION = 17;
+    private static final int SET_ERROR_CALLBACK = 18;
+    private static final int SET_PARAMETERS = 19;
+    private static final int GET_PARAMETERS = 20;
+    private static final int SET_PREVIEW_DISPLAY_ASYNC = 21;
+    private static final int SET_PREVIEW_CALLBACK = 22;
+    private static final int ENABLE_SHUTTER_SOUND = 23;
+    private static final int REFRESH_PARAMETERS = 24;
+
+    private Handler mCameraHandler;
+    private android.hardware.Camera mCamera;
+
+    // Used to retain a copy of Parameters for setting parameters.
+    private Parameters mParamsToSet;
+
+
+    // This holder is used when we need to pass the exception
+    // back to the calling thread. SynchornousQueue doesn't
+    // allow we to pass a null object thus a holder is needed.
+    private class IOExceptionHolder {
+        public IOException ex;
+    }
+
+    public static CameraManager instance() {
+        return sCameraManager;
+    }
+
+    private CameraManager() {
+        HandlerThread ht = new HandlerThread("Camera Handler Thread");
+        ht.start();
+        mCameraHandler = new CameraHandler(ht.getLooper());
+    }
+
+    private class CameraHandler extends Handler {
+        CameraHandler(Looper looper) {
+            super(looper);
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+        private void startFaceDetection() {
+            mCamera.startFaceDetection();
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+        private void stopFaceDetection() {
+            mCamera.stopFaceDetection();
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+        private void setFaceDetectionListener(FaceDetectionListener listener) {
+            mCamera.setFaceDetectionListener(listener);
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+        private void setPreviewTexture(Object surfaceTexture) {
+            try {
+                mCamera.setPreviewTexture((SurfaceTexture) surfaceTexture);
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN_MR1)
+        private void enableShutterSound(boolean enable) {
+            mCamera.enableShutterSound(enable);
+        }
+
+        /*
+         * This method does not deal with the build version check.  Everyone should
+         * check first before sending message to this handler.
+         */
+        @Override
+        public void handleMessage(final Message msg) {
+            try {
+                switch (msg.what) {
+                    case RELEASE:
+                        mCamera.release();
+                        mCamera = null;
+                        return;
+
+                    case RECONNECT:
+                        mReconnectIOException = null;
+                        try {
+                            mCamera.reconnect();
+                        } catch (IOException ex) {
+                            mReconnectIOException = ex;
+                        }
+                        return;
+
+                    case UNLOCK:
+                        mCamera.unlock();
+                        return;
+
+                    case LOCK:
+                        mCamera.lock();
+                        return;
+
+                    case SET_PREVIEW_TEXTURE_ASYNC:
+                        setPreviewTexture(msg.obj);
+                        return;
+
+                    case SET_PREVIEW_DISPLAY_ASYNC:
+                        try {
+                            mCamera.setPreviewDisplay((SurfaceHolder) msg.obj);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                        return;
+
+                    case START_PREVIEW_ASYNC:
+                        mCamera.startPreview();
+                        return;
+
+                    case STOP_PREVIEW:
+                        mCamera.stopPreview();
+                        return;
+
+                    case SET_PREVIEW_CALLBACK_WITH_BUFFER:
+                        mCamera.setPreviewCallbackWithBuffer(
+                            (PreviewCallback) msg.obj);
+                        return;
+
+                    case ADD_CALLBACK_BUFFER:
+                        mCamera.addCallbackBuffer((byte[]) msg.obj);
+                        return;
+
+                    case AUTO_FOCUS:
+                        mCamera.autoFocus((AutoFocusCallback) msg.obj);
+                        return;
+
+                    case CANCEL_AUTO_FOCUS:
+                        mCamera.cancelAutoFocus();
+                        return;
+
+                    case SET_AUTO_FOCUS_MOVE_CALLBACK:
+                        setAutoFocusMoveCallback(mCamera, msg.obj);
+                        return;
+
+                    case SET_DISPLAY_ORIENTATION:
+                        mCamera.setDisplayOrientation(msg.arg1);
+                        return;
+
+                    case SET_ZOOM_CHANGE_LISTENER:
+                        mCamera.setZoomChangeListener(
+                            (OnZoomChangeListener) msg.obj);
+                        return;
+
+                    case SET_FACE_DETECTION_LISTENER:
+                        setFaceDetectionListener((FaceDetectionListener) msg.obj);
+                        return;
+
+                    case START_FACE_DETECTION:
+                        startFaceDetection();
+                        return;
+
+                    case STOP_FACE_DETECTION:
+                        stopFaceDetection();
+                        return;
+
+                    case SET_ERROR_CALLBACK:
+                        mCamera.setErrorCallback((ErrorCallback) msg.obj);
+                        return;
+
+                    case SET_PARAMETERS:
+                        mParametersIsDirty = true;
+                        mParamsToSet.unflatten((String) msg.obj);
+                        mCamera.setParameters(mParamsToSet);
+                        return;
+
+                    case GET_PARAMETERS:
+                        if (mParametersIsDirty) {
+                            mParameters = mCamera.getParameters();
+                            mParametersIsDirty = false;
+                        }
+                        return;
+
+                    case SET_PREVIEW_CALLBACK:
+                        mCamera.setPreviewCallback((PreviewCallback) msg.obj);
+                        return;
+
+                    case ENABLE_SHUTTER_SOUND:
+                        enableShutterSound((msg.arg1 == 1) ? true : false);
+                        return;
+
+                    case REFRESH_PARAMETERS:
+                        mParametersIsDirty = true;
+                        return;
+
+                    default:
+                        throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
+                }
+            } catch (RuntimeException e) {
+                if (msg.what != RELEASE && mCamera != null) {
+                    try {
+                        mCamera.release();
+                    } catch (Exception ex) {
+                        Log.e(TAG, "Fail to release the camera.");
+                    }
+                    mCamera = null;
+                }
+                throw e;
+            }
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoFocusMoveCallback(android.hardware.Camera camera,
+            Object cb) {
+        camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
+    }
+
+    // Open camera synchronously. This method is invoked in the context of a
+    // background thread.
+    CameraProxy cameraOpen(int cameraId) {
+        // Cannot open camera in mCameraHandler, otherwise all camera events
+        // will be routed to mCameraHandler looper, which in turn will call
+        // event handler like Camera.onFaceDetection, which in turn will modify
+        // UI and cause exception like this:
+        // CalledFromWrongThreadException: Only the original thread that created
+        // a view hierarchy can touch its views.
+        mCamera = android.hardware.Camera.open(cameraId);
+        if (mCamera != null) {
+            mParametersIsDirty = true;
+            if (mParamsToSet == null) {
+                mParamsToSet = mCamera.getParameters();
+            }
+            return new CameraProxy();
+        } else {
+            return null;
+        }
+    }
+
+    public class CameraProxy {
+
+        private CameraProxy() {
+            Assert(mCamera != null);
+        }
+
+        public android.hardware.Camera getCamera() {
+            return mCamera;
+        }
+
+        public void release() {
+            // release() must be synchronous so we know exactly when the camera
+            // is released and can continue on.
+            mCameraHandler.sendEmptyMessage(RELEASE);
+            waitDone();
+        }
+
+        public void reconnect() throws IOException {
+            mCameraHandler.sendEmptyMessage(RECONNECT);
+            waitDone();
+            if (mReconnectIOException != null) {
+                throw mReconnectIOException;
+            }
+        }
+
+        public void unlock() {
+            mCameraHandler.sendEmptyMessage(UNLOCK);
+        }
+
+        public void lock() {
+            mCameraHandler.sendEmptyMessage(LOCK);
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+        public void setPreviewTextureAsync(final SurfaceTexture surfaceTexture) {
+            mCameraHandler.obtainMessage(SET_PREVIEW_TEXTURE_ASYNC, surfaceTexture).sendToTarget();
+        }
+
+        public void setPreviewDisplayAsync(final SurfaceHolder surfaceHolder) {
+            mCameraHandler.obtainMessage(SET_PREVIEW_DISPLAY_ASYNC, surfaceHolder).sendToTarget();
+        }
+
+        public void startPreviewAsync() {
+            mCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC);
+        }
+
+        // stopPreview() is synchronous because many resources should be released after
+        // the preview is stopped.
+        public void stopPreview() {
+            mCameraHandler.sendEmptyMessage(STOP_PREVIEW);
+            waitDone();
+        }
+
+        public void setPreviewCallback(final PreviewCallback cb) {
+            mCameraHandler.obtainMessage(SET_PREVIEW_CALLBACK, cb).sendToTarget();
+        }
+
+        public void setPreviewCallbackWithBuffer(final PreviewCallback cb) {
+            mCameraHandler.obtainMessage(SET_PREVIEW_CALLBACK_WITH_BUFFER, cb).sendToTarget();
+        }
+
+        public void addCallbackBuffer(byte[] callbackBuffer) {
+            mCameraHandler.obtainMessage(ADD_CALLBACK_BUFFER, callbackBuffer).sendToTarget();
+        }
+
+        public void autoFocus(AutoFocusCallback cb) {
+            mCameraHandler.obtainMessage(AUTO_FOCUS, cb).sendToTarget();
+        }
+
+        public void cancelAutoFocus() {
+            mCameraHandler.removeMessages(AUTO_FOCUS);
+            mCameraHandler.sendEmptyMessage(CANCEL_AUTO_FOCUS);
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+        public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
+            mCameraHandler.obtainMessage(SET_AUTO_FOCUS_MOVE_CALLBACK, cb).sendToTarget();
+        }
+
+        public void takePicture(final ShutterCallback shutter, final PictureCallback raw,
+                final PictureCallback postview, final PictureCallback jpeg) {
+            // Too many parameters, so use post for simplicity
+            mCameraHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCamera.takePicture(shutter, raw, postview, jpeg);
+                }
+            });
+        }
+
+        public void takePicture2(final ShutterCallback shutter, final PictureCallback raw,
+                final PictureCallback postview, final PictureCallback jpeg,
+                final int cameraState, final int focusState) {
+            // Too many parameters, so use post for simplicity
+            mCameraHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        mCamera.takePicture(shutter, raw, postview, jpeg);
+                    } catch (RuntimeException e) {
+                        Log.w(TAG, "take picture failed; cameraState:" + cameraState
+                            + ", focusState:" + focusState);
+                        throw e;
+                    }
+                }
+            });
+        }
+
+        public void setDisplayOrientation(int degrees) {
+            mCameraHandler.obtainMessage(SET_DISPLAY_ORIENTATION, degrees, 0)
+                    .sendToTarget();
+        }
+
+        public void setZoomChangeListener(OnZoomChangeListener listener) {
+            mCameraHandler.obtainMessage(SET_ZOOM_CHANGE_LISTENER, listener).sendToTarget();
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+        public void setFaceDetectionListener(FaceDetectionListener listener) {
+            mCameraHandler.obtainMessage(SET_FACE_DETECTION_LISTENER, listener).sendToTarget();
+        }
+
+        public void startFaceDetection() {
+            mCameraHandler.sendEmptyMessage(START_FACE_DETECTION);
+        }
+
+        public void stopFaceDetection() {
+            mCameraHandler.sendEmptyMessage(STOP_FACE_DETECTION);
+        }
+
+        public void setErrorCallback(ErrorCallback cb) {
+            mCameraHandler.obtainMessage(SET_ERROR_CALLBACK, cb).sendToTarget();
+        }
+
+        public void setParameters(Parameters params) {
+            if (params == null) {
+                Log.v(TAG, "null parameters in setParameters()");
+                return;
+            }
+            mCameraHandler.obtainMessage(SET_PARAMETERS, params.flatten())
+                    .sendToTarget();
+        }
+
+        public Parameters getParameters() {
+            mCameraHandler.sendEmptyMessage(GET_PARAMETERS);
+            waitDone();
+            return mParameters;
+        }
+
+        public void refreshParameters() {
+            mCameraHandler.sendEmptyMessage(REFRESH_PARAMETERS);
+        }
+
+        public void enableShutterSound(boolean enable) {
+            mCameraHandler.obtainMessage(
+                    ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0).sendToTarget();
+        }
+
+        // return false if cancelled.
+        public boolean waitDone() {
+            final Object waitDoneLock = new Object();
+            final Runnable unlockRunnable = new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (waitDoneLock) {
+                        waitDoneLock.notifyAll();
+                    }
+                }
+            };
+
+            synchronized (waitDoneLock) {
+                mCameraHandler.post(unlockRunnable);
+                try {
+                    waitDoneLock.wait();
+                } catch (InterruptedException ex) {
+                    Log.v(TAG, "waitDone interrupted");
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
new file mode 100644
index 0000000..3275d5f
--- /dev/null
+++ b/src/com/android/camera/CameraModule.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 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.Intent;
+import android.content.res.Configuration;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+public interface CameraModule {
+
+    public void init(CameraActivity activity, View frame, boolean reuseScreenNail);
+
+    public void onFullScreenChanged(boolean full);
+
+    public void onPauseBeforeSuper();
+
+    public void onPauseAfterSuper();
+
+    public void onResumeBeforeSuper();
+
+    public void onResumeAfterSuper();
+
+    public void onConfigurationChanged(Configuration config);
+
+    public void onStop();
+
+    public void installIntentFilter();
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data);
+
+    public boolean onBackPressed();
+
+    public boolean onKeyDown(int keyCode, KeyEvent event);
+
+    public boolean onKeyUp(int keyCode, KeyEvent event);
+
+    public void onSingleTapUp(View view, int x, int y);
+
+    public boolean dispatchTouchEvent(MotionEvent m);
+
+    public void onPreviewTextureCopied();
+
+    public void onCaptureTextureCopied();
+
+    public void onUserInteraction();
+
+    public boolean updateStorageHintOnResume();
+
+    public void updateCameraAppView();
+
+    public boolean needsSwitcher();
+
+    public boolean needsPieMenu();
+
+    public void onOrientationChanged(int orientation);
+
+    public void onShowSwitcherPopup();
+
+    public void onMediaSaveServiceConnected(MediaSaveService s);
+}
diff --git a/src/com/android/camera/CameraPreference.java b/src/com/android/camera/CameraPreference.java
new file mode 100644
index 0000000..5ddd86d
--- /dev/null
+++ b/src/com/android/camera/CameraPreference.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.R;
+
+/**
+ * The base class of all Preferences used in Camera. The preferences can be
+ * loaded from XML resource by <code>PreferenceInflater</code>.
+ */
+public abstract class CameraPreference {
+
+    private final String mTitle;
+    private SharedPreferences mSharedPreferences;
+    private final Context mContext;
+
+    static public interface OnPreferenceChangedListener {
+        public void onSharedPreferenceChanged();
+        public void onRestorePreferencesClicked();
+        public void onOverriddenPreferencesClicked();
+        public void onCameraPickerClicked(int cameraId);
+    }
+
+    public CameraPreference(Context context, AttributeSet attrs) {
+        mContext = context;
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.CameraPreference, 0, 0);
+        mTitle = a.getString(R.styleable.CameraPreference_title);
+        a.recycle();
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public SharedPreferences getSharedPreferences() {
+        if (mSharedPreferences == null) {
+            mSharedPreferences = ComboPreferences.get(mContext);
+        }
+        return mSharedPreferences;
+    }
+
+    public abstract void reloadValue();
+}
diff --git a/src/com/android/camera/CameraScreenNail.java b/src/com/android/camera/CameraScreenNail.java
new file mode 100644
index 0000000..993a7d3
--- /dev/null
+++ b/src/com/android/camera/CameraScreenNail.java
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2012 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.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
+import com.android.gallery3d.ui.SurfaceTextureScreenNail;
+
+/*
+ * This is a ScreenNail which can display camera's preview.
+ */
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+public class CameraScreenNail extends SurfaceTextureScreenNail {
+    private static final String TAG = "CAM_ScreenNail";
+    private static final int ANIM_NONE = 0;
+    // Capture animation is about to start.
+    private static final int ANIM_CAPTURE_START = 1;
+    // Capture animation is running.
+    private static final int ANIM_CAPTURE_RUNNING = 2;
+    // Switch camera animation needs to copy texture.
+    private static final int ANIM_SWITCH_COPY_TEXTURE = 3;
+    // Switch camera animation shows the initial feedback by darkening the
+    // preview.
+    private static final int ANIM_SWITCH_DARK_PREVIEW = 4;
+    // Switch camera animation is waiting for the first frame.
+    private static final int ANIM_SWITCH_WAITING_FIRST_FRAME = 5;
+    // Switch camera animation is about to start.
+    private static final int ANIM_SWITCH_START = 6;
+    // Switch camera animation is running.
+    private static final int ANIM_SWITCH_RUNNING = 7;
+
+    private boolean mVisible;
+    // True if first onFrameAvailable has been called. If screen nail is drawn
+    // too early, it will be all white.
+    private boolean mFirstFrameArrived;
+    private Listener mListener;
+    private final float[] mTextureTransformMatrix = new float[16];
+
+    // Animation.
+    private CaptureAnimManager mCaptureAnimManager;
+    private SwitchAnimManager mSwitchAnimManager = new SwitchAnimManager();
+    private int mAnimState = ANIM_NONE;
+    private RawTexture mAnimTexture;
+    // Some methods are called by GL thread and some are called by main thread.
+    // This protects mAnimState, mVisible, and surface texture. This also makes
+    // sure some code are atomic. For example, requestRender and setting
+    // mAnimState.
+    private Object mLock = new Object();
+
+    private OnFrameDrawnListener mOneTimeFrameDrawnListener;
+    private int mRenderWidth;
+    private int mRenderHeight;
+    // This represents the scaled, uncropped size of the texture
+    // Needed for FaceView
+    private int mUncroppedRenderWidth;
+    private int mUncroppedRenderHeight;
+    private float mScaleX = 1f, mScaleY = 1f;
+    private boolean mFullScreen;
+    private boolean mEnableAspectRatioClamping = false;
+    private boolean mAcquireTexture = false;
+    private final DrawClient mDefaultDraw = new DrawClient() {
+        @Override
+        public void onDraw(GLCanvas canvas, int x, int y, int width, int height) {
+            CameraScreenNail.super.draw(canvas, x, y, width, height);
+        }
+
+        @Override
+        public boolean requiresSurfaceTexture() {
+            return true;
+        }
+
+        @Override
+        public RawTexture copyToTexture(GLCanvas c, RawTexture texture, int w, int h) {
+            // We shouldn't be here since requireSurfaceTexture() returns true.
+            return null;
+        }
+    };
+    private DrawClient mDraw = mDefaultDraw;
+    private float mAlpha = 1f;
+    private Runnable mOnFrameDrawnListener;
+
+    public interface Listener {
+        void requestRender();
+        // Preview has been copied to a texture.
+        void onPreviewTextureCopied();
+
+        void onCaptureTextureCopied();
+    }
+
+    public interface OnFrameDrawnListener {
+        void onFrameDrawn(CameraScreenNail c);
+    }
+
+    public interface DrawClient {
+        void onDraw(GLCanvas canvas, int x, int y, int width, int height);
+
+        boolean requiresSurfaceTexture();
+        // The client should implement this if requiresSurfaceTexture() is false;
+        RawTexture copyToTexture(GLCanvas c, RawTexture texture, int width, int height);
+    }
+
+    public CameraScreenNail(Listener listener, Context ctx) {
+        mListener = listener;
+        mCaptureAnimManager = new CaptureAnimManager(ctx);
+    }
+
+    public void setFullScreen(boolean full) {
+        synchronized (mLock) {
+            mFullScreen = full;
+        }
+    }
+
+    /**
+     * returns the uncropped, but scaled, width of the rendered texture
+     */
+    public int getUncroppedRenderWidth() {
+        return mUncroppedRenderWidth;
+    }
+
+    /**
+     * returns the uncropped, but scaled, width of the rendered texture
+     */
+    public int getUncroppedRenderHeight() {
+        return mUncroppedRenderHeight;
+    }
+
+    @Override
+    public int getWidth() {
+        return mEnableAspectRatioClamping ? mRenderWidth : getTextureWidth();
+    }
+
+    @Override
+    public int getHeight() {
+        return mEnableAspectRatioClamping ? mRenderHeight : getTextureHeight();
+    }
+
+    private int getTextureWidth() {
+        return super.getWidth();
+    }
+
+    private int getTextureHeight() {
+        return super.getHeight();
+    }
+
+    @Override
+    public void setSize(int w, int h) {
+        super.setSize(w,  h);
+        mEnableAspectRatioClamping = false;
+        if (mRenderWidth == 0) {
+            mRenderWidth = w;
+            mRenderHeight = h;
+        }
+        updateRenderSize();
+    }
+
+    /**
+     * Tells the ScreenNail to override the default aspect ratio scaling
+     * and instead perform custom scaling to basically do a centerCrop instead
+     * of the default centerInside
+     *
+     * Note that calls to setSize will disable this
+     */
+    public void enableAspectRatioClamping() {
+        mEnableAspectRatioClamping = true;
+        updateRenderSize();
+    }
+
+    private void setPreviewLayoutSize(int w, int h) {
+        Log.i(TAG, "preview layout size: "+w+"/"+h);
+        mRenderWidth = w;
+        mRenderHeight = h;
+        updateRenderSize();
+    }
+
+    private void updateRenderSize() {
+        if (!mEnableAspectRatioClamping) {
+            mScaleX = mScaleY = 1f;
+            mUncroppedRenderWidth = getTextureWidth();
+            mUncroppedRenderHeight = getTextureHeight();
+            Log.i(TAG, "aspect ratio clamping disabled");
+            return;
+        }
+
+        float aspectRatio;
+        if (getTextureWidth() > getTextureHeight()) {
+            aspectRatio = (float) getTextureWidth() / (float) getTextureHeight();
+        } else {
+            aspectRatio = (float) getTextureHeight() / (float) getTextureWidth();
+        }
+        float scaledTextureWidth, scaledTextureHeight;
+        if (mRenderWidth > mRenderHeight) {
+            scaledTextureWidth = Math.max(mRenderWidth,
+                    (int) (mRenderHeight * aspectRatio));
+            scaledTextureHeight = Math.max(mRenderHeight,
+                    (int)(mRenderWidth / aspectRatio));
+        } else {
+            scaledTextureWidth = Math.max(mRenderWidth,
+                    (int) (mRenderHeight / aspectRatio));
+            scaledTextureHeight = Math.max(mRenderHeight,
+                    (int) (mRenderWidth * aspectRatio));
+        }
+        mScaleX = mRenderWidth / scaledTextureWidth;
+        mScaleY = mRenderHeight / scaledTextureHeight;
+        mUncroppedRenderWidth = Math.round(scaledTextureWidth);
+        mUncroppedRenderHeight = Math.round(scaledTextureHeight);
+        Log.i(TAG, "aspect ratio clamping enabled, surfaceTexture scale: " + mScaleX + ", " + mScaleY);
+    }
+
+    public void acquireSurfaceTexture() {
+        synchronized (mLock) {
+            mFirstFrameArrived = false;
+            mAnimTexture = new RawTexture(getTextureWidth(), getTextureHeight(), true);
+            mAcquireTexture = true;
+        }
+        mListener.requestRender();
+    }
+
+    @Override
+    public void releaseSurfaceTexture() {
+        synchronized (mLock) {
+            if (mAcquireTexture) {
+                mAcquireTexture = false;
+                mLock.notifyAll();
+            } else {
+                if (super.getSurfaceTexture() != null) {
+                    super.releaseSurfaceTexture();
+                }
+                mAnimState = ANIM_NONE; // stop the animation
+            }
+        }
+    }
+
+    public void copyTexture() {
+        synchronized (mLock) {
+            mListener.requestRender();
+            mAnimState = ANIM_SWITCH_COPY_TEXTURE;
+        }
+    }
+
+    public void animateSwitchCamera() {
+        Log.v(TAG, "animateSwitchCamera");
+        synchronized (mLock) {
+            if (mAnimState == ANIM_SWITCH_DARK_PREVIEW) {
+                // Do not request render here because camera has been just
+                // started. We do not want to draw black frames.
+                mAnimState = ANIM_SWITCH_WAITING_FIRST_FRAME;
+            }
+        }
+    }
+
+    public void animateCapture(int displayRotation) {
+        synchronized (mLock) {
+            mCaptureAnimManager.setOrientation(displayRotation);
+            mCaptureAnimManager.animateFlashAndSlide();
+            mListener.requestRender();
+            mAnimState = ANIM_CAPTURE_START;
+        }
+    }
+
+    public RawTexture getAnimationTexture() {
+        return mAnimTexture;
+    }
+
+    public void animateFlash(int displayRotation) {
+        synchronized (mLock) {
+            mCaptureAnimManager.setOrientation(displayRotation);
+            mCaptureAnimManager.animateFlash();
+            mListener.requestRender();
+            mAnimState = ANIM_CAPTURE_START;
+        }
+    }
+
+    public void animateSlide() {
+        synchronized (mLock) {
+            mCaptureAnimManager.animateSlide();
+            mListener.requestRender();
+        }
+    }
+
+    private void callbackIfNeeded() {
+        if (mOneTimeFrameDrawnListener != null) {
+            mOneTimeFrameDrawnListener.onFrameDrawn(this);
+            mOneTimeFrameDrawnListener = null;
+        }
+    }
+
+    @Override
+    protected void updateTransformMatrix(float[] matrix) {
+        super.updateTransformMatrix(matrix);
+        Matrix.translateM(matrix, 0, .5f, .5f, 0);
+        Matrix.scaleM(matrix, 0, mScaleX, mScaleY, 1f);
+        Matrix.translateM(matrix, 0, -.5f, -.5f, 0);
+    }
+
+    public void directDraw(GLCanvas canvas, int x, int y, int width, int height) {
+        DrawClient draw;
+        synchronized (mLock) {
+            draw = mDraw;
+        }
+        draw.onDraw(canvas, x, y, width, height);
+    }
+
+    public void setDraw(DrawClient draw) {
+        synchronized (mLock) {
+            if (draw == null) {
+                mDraw = mDefaultDraw;
+            } else {
+                mDraw = draw;
+            }
+        }
+        mListener.requestRender();
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+        synchronized (mLock) {
+            allocateTextureIfRequested(canvas);
+            if (!mVisible) mVisible = true;
+            SurfaceTexture surfaceTexture = getSurfaceTexture();
+            if (mDraw.requiresSurfaceTexture() && (surfaceTexture == null || !mFirstFrameArrived)) {
+                return;
+            }
+            if (mOnFrameDrawnListener != null) {
+                mOnFrameDrawnListener.run();
+                mOnFrameDrawnListener = null;
+            }
+            float oldAlpha = canvas.getAlpha();
+            canvas.setAlpha(mAlpha);
+
+            switch (mAnimState) {
+                case ANIM_NONE:
+                    directDraw(canvas, x, y, width, height);
+                    break;
+                case ANIM_SWITCH_COPY_TEXTURE:
+                    copyPreviewTexture(canvas);
+                    mSwitchAnimManager.setReviewDrawingSize(width, height);
+                    mListener.onPreviewTextureCopied();
+                    mAnimState = ANIM_SWITCH_DARK_PREVIEW;
+                    // The texture is ready. Fall through to draw darkened
+                    // preview.
+                case ANIM_SWITCH_DARK_PREVIEW:
+                case ANIM_SWITCH_WAITING_FIRST_FRAME:
+                    // Consume the frame. If the buffers are full,
+                    // onFrameAvailable will not be called. Animation state
+                    // relies on onFrameAvailable.
+                    surfaceTexture.updateTexImage();
+                    mSwitchAnimManager.drawDarkPreview(canvas, x, y, width,
+                            height, mAnimTexture);
+                    break;
+                case ANIM_SWITCH_START:
+                    mSwitchAnimManager.startAnimation();
+                    mAnimState = ANIM_SWITCH_RUNNING;
+                    break;
+                case ANIM_CAPTURE_START:
+                    copyPreviewTexture(canvas);
+                    mListener.onCaptureTextureCopied();
+                    mCaptureAnimManager.startAnimation();
+                    mAnimState = ANIM_CAPTURE_RUNNING;
+                    break;
+            }
+
+            if (mAnimState == ANIM_CAPTURE_RUNNING || mAnimState == ANIM_SWITCH_RUNNING) {
+                boolean drawn;
+                if (mAnimState == ANIM_CAPTURE_RUNNING) {
+                    if (!mFullScreen) {
+                        // Skip the animation if no longer in full screen mode
+                        drawn = false;
+                    } else {
+                        drawn = mCaptureAnimManager.drawAnimation(canvas, this, mAnimTexture,
+                                x, y, width, height);
+                    }
+                } else {
+                    drawn = mSwitchAnimManager.drawAnimation(canvas, x, y,
+                            width, height, this, mAnimTexture);
+                }
+                if (drawn) {
+                    mListener.requestRender();
+                } else {
+                    // Continue to the normal draw procedure if the animation is
+                    // not drawn.
+                    mAnimState = ANIM_NONE;
+                    directDraw(canvas, x, y, width, height);
+                }
+            }
+            canvas.setAlpha(oldAlpha);
+            callbackIfNeeded();
+        } // mLock
+    }
+
+    private void copyPreviewTexture(GLCanvas canvas) {
+        if (!mDraw.requiresSurfaceTexture()) {
+            mAnimTexture =  mDraw.copyToTexture(
+                    canvas, mAnimTexture, getTextureWidth(), getTextureHeight());
+        } else {
+            int width = mAnimTexture.getWidth();
+            int height = mAnimTexture.getHeight();
+            canvas.beginRenderTarget(mAnimTexture);
+            // Flip preview texture vertically. OpenGL uses bottom left point
+            // as the origin (0, 0).
+            canvas.translate(0, height);
+            canvas.scale(1, -1, 1);
+            getSurfaceTexture().getTransformMatrix(mTextureTransformMatrix);
+            updateTransformMatrix(mTextureTransformMatrix);
+            canvas.drawTexture(mExtTexture, mTextureTransformMatrix, 0, 0, width, height);
+            canvas.endRenderTarget();
+        }
+    }
+
+    @Override
+    public void noDraw() {
+        synchronized (mLock) {
+            mVisible = false;
+        }
+    }
+
+    @Override
+    public void recycle() {
+        synchronized (mLock) {
+            mVisible = false;
+        }
+    }
+
+    @Override
+    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
+        synchronized (mLock) {
+            if (getSurfaceTexture() != surfaceTexture) {
+                return;
+            }
+            mFirstFrameArrived = true;
+            if (mVisible) {
+                if (mAnimState == ANIM_SWITCH_WAITING_FIRST_FRAME) {
+                    mAnimState = ANIM_SWITCH_START;
+                }
+                // We need to ask for re-render if the SurfaceTexture receives a new
+                // frame.
+                mListener.requestRender();
+            }
+        }
+    }
+
+    // We need to keep track of the size of preview frame on the screen because
+    // it's needed when we do switch-camera animation. See comments in
+    // SwitchAnimManager.java. This is based on the natural orientation, not the
+    // view system orientation.
+    public void setPreviewFrameLayoutSize(int width, int height) {
+        synchronized (mLock) {
+            mSwitchAnimManager.setPreviewFrameLayoutSize(width, height);
+            setPreviewLayoutSize(width, height);
+        }
+    }
+
+    public void setOneTimeOnFrameDrawnListener(OnFrameDrawnListener l) {
+        synchronized (mLock) {
+            mFirstFrameArrived = false;
+            mOneTimeFrameDrawnListener = l;
+        }
+    }
+
+    @Override
+    public SurfaceTexture getSurfaceTexture() {
+        synchronized (mLock) {
+            SurfaceTexture surfaceTexture = super.getSurfaceTexture();
+            if (surfaceTexture == null && mAcquireTexture) {
+                try {
+                    mLock.wait();
+                    surfaceTexture = super.getSurfaceTexture();
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "unexpected interruption");
+                }
+            }
+            return surfaceTexture;
+        }
+    }
+
+    private void allocateTextureIfRequested(GLCanvas canvas) {
+        synchronized (mLock) {
+            if (mAcquireTexture) {
+                super.acquireSurfaceTexture(canvas);
+                mAcquireTexture = false;
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    public void setOnFrameDrawnOneShot(Runnable run) {
+        synchronized (mLock) {
+            mOnFrameDrawnListener = run;
+        }
+    }
+
+    public float getAlpha() {
+        synchronized (mLock) {
+            return mAlpha;
+        }
+    }
+
+    public void setAlpha(float alpha) {
+        synchronized (mLock) {
+            mAlpha = alpha;
+            mListener.requestRender();
+        }
+    }
+}
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
new file mode 100644
index 0000000..4e9a5dd
--- /dev/null
+++ b/src/com/android/camera/CameraSettings.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.media.CamcorderProfile;
+import android.util.FloatMath;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ *  Provides utilities and keys for Camera settings.
+ */
+public class CameraSettings {
+    private static final int NOT_FOUND = -1;
+
+    public static final String KEY_VERSION = "pref_version_key";
+    public static final String KEY_LOCAL_VERSION = "pref_local_version_key";
+    public static final String KEY_RECORD_LOCATION = "pref_camera_recordlocation_key";
+    public static final String KEY_VIDEO_QUALITY = "pref_video_quality_key";
+    public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL = "pref_video_time_lapse_frame_interval_key";
+    public static final String KEY_PICTURE_SIZE = "pref_camera_picturesize_key";
+    public static final String KEY_JPEG_QUALITY = "pref_camera_jpegquality_key";
+    public static final String KEY_FOCUS_MODE = "pref_camera_focusmode_key";
+    public static final String KEY_FLASH_MODE = "pref_camera_flashmode_key";
+    public static final String KEY_VIDEOCAMERA_FLASH_MODE = "pref_camera_video_flashmode_key";
+    public static final String KEY_WHITE_BALANCE = "pref_camera_whitebalance_key";
+    public static final String KEY_SCENE_MODE = "pref_camera_scenemode_key";
+    public static final String KEY_EXPOSURE = "pref_camera_exposure_key";
+    public static final String KEY_TIMER = "pref_camera_timer_key";
+    public static final String KEY_TIMER_SOUND_EFFECTS = "pref_camera_timer_sound_key";
+    public static final String KEY_VIDEO_EFFECT = "pref_video_effect_key";
+    public static final String KEY_CAMERA_ID = "pref_camera_id_key";
+    public static final String KEY_CAMERA_HDR = "pref_camera_hdr_key";
+    public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN = "pref_camera_first_use_hint_shown_key";
+    public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN = "pref_video_first_use_hint_shown_key";
+    public static final String KEY_PHOTOSPHERE_PICTURESIZE = "pref_photosphere_picturesize_key";
+
+    public static final String EXPOSURE_DEFAULT_VALUE = "0";
+
+    public static final int CURRENT_VERSION = 5;
+    public static final int CURRENT_LOCAL_VERSION = 2;
+
+    private static final String TAG = "CameraSettings";
+
+    private final Context mContext;
+    private final Parameters mParameters;
+    private final CameraInfo[] mCameraInfo;
+    private final int mCameraId;
+
+    public CameraSettings(Activity activity, Parameters parameters,
+                          int cameraId, CameraInfo[] cameraInfo) {
+        mContext = activity;
+        mParameters = parameters;
+        mCameraId = cameraId;
+        mCameraInfo = cameraInfo;
+    }
+
+    public PreferenceGroup getPreferenceGroup(int preferenceRes) {
+        PreferenceInflater inflater = new PreferenceInflater(mContext);
+        PreferenceGroup group =
+                (PreferenceGroup) inflater.inflate(preferenceRes);
+        if (mParameters != null) initPreference(group);
+        return group;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    public static String getDefaultVideoQuality(int cameraId,
+            String defaultQuality) {
+        if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
+            if (CamcorderProfile.hasProfile(
+                    cameraId, Integer.valueOf(defaultQuality))) {
+                return defaultQuality;
+            }
+        }
+        return Integer.toString(CamcorderProfile.QUALITY_HIGH);
+    }
+
+    public static void initialCameraPictureSize(
+            Context context, Parameters parameters) {
+        // When launching the camera app first time, we will set the picture
+        // size to the first one in the list defined in "arrays.xml" and is also
+        // supported by the driver.
+        List<Size> supported = parameters.getSupportedPictureSizes();
+        if (supported == null) return;
+        for (String candidate : context.getResources().getStringArray(
+                R.array.pref_camera_picturesize_entryvalues)) {
+            if (setCameraPictureSize(candidate, supported, parameters)) {
+                SharedPreferences.Editor editor = ComboPreferences
+                        .get(context).edit();
+                editor.putString(KEY_PICTURE_SIZE, candidate);
+                editor.apply();
+                return;
+            }
+        }
+        Log.e(TAG, "No supported picture size found");
+    }
+
+    public static void removePreferenceFromScreen(
+            PreferenceGroup group, String key) {
+        removePreference(group, key);
+    }
+
+    public static boolean setCameraPictureSize(
+            String candidate, List<Size> supported, Parameters parameters) {
+        int index = candidate.indexOf('x');
+        if (index == NOT_FOUND) return false;
+        int width = Integer.parseInt(candidate.substring(0, index));
+        int height = Integer.parseInt(candidate.substring(index + 1));
+        for (Size size : supported) {
+            if (size.width == width && size.height == height) {
+                parameters.setPictureSize(width, height);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static int getMaxVideoDuration(Context context) {
+        int duration = 0;  // in milliseconds, 0 means unlimited.
+        try {
+            duration = context.getResources().getInteger(R.integer.max_video_recording_length);
+        } catch (Resources.NotFoundException ex) {
+        }
+        return duration;
+    }
+
+    private void initPreference(PreferenceGroup group) {
+        ListPreference videoQuality = group.findPreference(KEY_VIDEO_QUALITY);
+        ListPreference timeLapseInterval = group.findPreference(KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL);
+        ListPreference pictureSize = group.findPreference(KEY_PICTURE_SIZE);
+        ListPreference whiteBalance =  group.findPreference(KEY_WHITE_BALANCE);
+        ListPreference sceneMode = group.findPreference(KEY_SCENE_MODE);
+        ListPreference flashMode = group.findPreference(KEY_FLASH_MODE);
+        ListPreference focusMode = group.findPreference(KEY_FOCUS_MODE);
+        IconListPreference exposure =
+                (IconListPreference) group.findPreference(KEY_EXPOSURE);
+        CountDownTimerPreference timer =
+                (CountDownTimerPreference) group.findPreference(KEY_TIMER);
+        ListPreference countDownSoundEffects = group.findPreference(KEY_TIMER_SOUND_EFFECTS);
+        IconListPreference cameraIdPref =
+                (IconListPreference) group.findPreference(KEY_CAMERA_ID);
+        ListPreference videoFlashMode =
+                group.findPreference(KEY_VIDEOCAMERA_FLASH_MODE);
+        ListPreference videoEffect = group.findPreference(KEY_VIDEO_EFFECT);
+        ListPreference cameraHdr = group.findPreference(KEY_CAMERA_HDR);
+
+        // Since the screen could be loaded from different resources, we need
+        // to check if the preference is available here
+        if (videoQuality != null) {
+            filterUnsupportedOptions(group, videoQuality, getSupportedVideoQuality());
+        }
+
+        if (pictureSize != null) {
+            filterUnsupportedOptions(group, pictureSize, sizeListToStringList(
+                    mParameters.getSupportedPictureSizes()));
+            filterSimilarPictureSize(group, pictureSize);
+        }
+        if (whiteBalance != null) {
+            filterUnsupportedOptions(group,
+                    whiteBalance, mParameters.getSupportedWhiteBalance());
+        }
+        if (sceneMode != null) {
+            filterUnsupportedOptions(group,
+                    sceneMode, mParameters.getSupportedSceneModes());
+        }
+        if (flashMode != null) {
+            filterUnsupportedOptions(group,
+                    flashMode, mParameters.getSupportedFlashModes());
+        }
+        if (focusMode != null) {
+            if (!Util.isFocusAreaSupported(mParameters)) {
+                filterUnsupportedOptions(group,
+                        focusMode, mParameters.getSupportedFocusModes());
+            } else {
+                // Remove the focus mode if we can use tap-to-focus.
+                removePreference(group, focusMode.getKey());
+            }
+        }
+        if (videoFlashMode != null) {
+            filterUnsupportedOptions(group,
+                    videoFlashMode, mParameters.getSupportedFlashModes());
+        }
+        if (exposure != null) buildExposureCompensation(group, exposure);
+        if (cameraIdPref != null) buildCameraId(group, cameraIdPref);
+
+        if (timeLapseInterval != null) {
+            if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
+                resetIfInvalid(timeLapseInterval);
+            } else {
+                removePreference(group, timeLapseInterval.getKey());
+            }
+        }
+        if (videoEffect != null) {
+            if (ApiHelper.HAS_EFFECTS_RECORDING) {
+                initVideoEffect(group, videoEffect);
+                resetIfInvalid(videoEffect);
+            } else {
+                filterUnsupportedOptions(group, videoEffect, null);
+            }
+        }
+        if (cameraHdr != null && (!ApiHelper.HAS_CAMERA_HDR
+                    || !Util.isCameraHdrSupported(mParameters))) {
+            removePreference(group, cameraHdr.getKey());
+        }
+    }
+
+    private void buildExposureCompensation(
+            PreferenceGroup group, IconListPreference exposure) {
+        int max = mParameters.getMaxExposureCompensation();
+        int min = mParameters.getMinExposureCompensation();
+        if (max == 0 && min == 0) {
+            removePreference(group, exposure.getKey());
+            return;
+        }
+        float step = mParameters.getExposureCompensationStep();
+
+        // show only integer values for exposure compensation
+        int maxValue = Math.min(3, (int) FloatMath.floor(max * step));
+        int minValue = Math.max(-3, (int) FloatMath.ceil(min * step));
+        String explabel = mContext.getResources().getString(R.string.pref_exposure_label);
+        CharSequence entries[] = new CharSequence[maxValue - minValue + 1];
+        CharSequence entryValues[] = new CharSequence[maxValue - minValue + 1];
+        CharSequence labels[] = new CharSequence[maxValue - minValue + 1];
+        int[] icons = new int[maxValue - minValue + 1];
+        TypedArray iconIds = mContext.getResources().obtainTypedArray(
+                R.array.pref_camera_exposure_icons);
+        for (int i = minValue; i <= maxValue; ++i) {
+            entryValues[i - minValue] = Integer.toString(Math.round(i / step));
+            StringBuilder builder = new StringBuilder();
+            if (i > 0) builder.append('+');
+            entries[i - minValue] = builder.append(i).toString();
+            labels[i - minValue] = explabel + " " + builder.toString();
+            icons[i - minValue] = iconIds.getResourceId(3 + i, 0);
+        }
+        exposure.setUseSingleIcon(true);
+        exposure.setEntries(entries);
+        exposure.setLabels(labels);
+        exposure.setEntryValues(entryValues);
+        exposure.setLargeIconIds(icons);
+    }
+
+    private void buildCameraId(
+            PreferenceGroup group, IconListPreference preference) {
+        int numOfCameras = mCameraInfo.length;
+        if (numOfCameras < 2) {
+            removePreference(group, preference.getKey());
+            return;
+        }
+
+        CharSequence[] entryValues = new CharSequence[numOfCameras];
+        for (int i = 0; i < numOfCameras; ++i) {
+            entryValues[i] = "" + i;
+        }
+        preference.setEntryValues(entryValues);
+    }
+
+    private static boolean removePreference(PreferenceGroup group, String key) {
+        for (int i = 0, n = group.size(); i < n; i++) {
+            CameraPreference child = group.get(i);
+            if (child instanceof PreferenceGroup) {
+                if (removePreference((PreferenceGroup) child, key)) {
+                    return true;
+                }
+            }
+            if (child instanceof ListPreference &&
+                    ((ListPreference) child).getKey().equals(key)) {
+                group.removePreference(i);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void filterUnsupportedOptions(PreferenceGroup group,
+            ListPreference pref, List<String> supported) {
+
+        // Remove the preference if the parameter is not supported or there is
+        // only one options for the settings.
+        if (supported == null || supported.size() <= 1) {
+            removePreference(group, pref.getKey());
+            return;
+        }
+
+        pref.filterUnsupported(supported);
+        if (pref.getEntries().length <= 1) {
+            removePreference(group, pref.getKey());
+            return;
+        }
+
+        resetIfInvalid(pref);
+    }
+
+    private void filterSimilarPictureSize(PreferenceGroup group,
+            ListPreference pref) {
+        pref.filterDuplicated();
+        if (pref.getEntries().length <= 1) {
+            removePreference(group, pref.getKey());
+            return;
+        }
+        resetIfInvalid(pref);
+    }
+
+    private void resetIfInvalid(ListPreference pref) {
+        // Set the value to the first entry if it is invalid.
+        String value = pref.getValue();
+        if (pref.findIndexOfValue(value) == NOT_FOUND) {
+            pref.setValueIndex(0);
+        }
+    }
+
+    private static List<String> sizeListToStringList(List<Size> sizes) {
+        ArrayList<String> list = new ArrayList<String>();
+        for (Size size : sizes) {
+            list.add(String.format(Locale.ENGLISH, "%dx%d", size.width, size.height));
+        }
+        return list;
+    }
+
+    public static void upgradeLocalPreferences(SharedPreferences pref) {
+        int version;
+        try {
+            version = pref.getInt(KEY_LOCAL_VERSION, 0);
+        } catch (Exception ex) {
+            version = 0;
+        }
+        if (version == CURRENT_LOCAL_VERSION) return;
+
+        SharedPreferences.Editor editor = pref.edit();
+        if (version == 1) {
+            // We use numbers to represent the quality now. The quality definition is identical to
+            // that of CamcorderProfile.java.
+            editor.remove("pref_video_quality_key");
+        }
+        editor.putInt(KEY_LOCAL_VERSION, CURRENT_LOCAL_VERSION);
+        editor.apply();
+    }
+
+    public static void upgradeGlobalPreferences(SharedPreferences pref) {
+        upgradeOldVersion(pref);
+        upgradeCameraId(pref);
+    }
+
+    private static void upgradeOldVersion(SharedPreferences pref) {
+        int version;
+        try {
+            version = pref.getInt(KEY_VERSION, 0);
+        } catch (Exception ex) {
+            version = 0;
+        }
+        if (version == CURRENT_VERSION) return;
+
+        SharedPreferences.Editor editor = pref.edit();
+        if (version == 0) {
+            // We won't use the preference which change in version 1.
+            // So, just upgrade to version 1 directly
+            version = 1;
+        }
+        if (version == 1) {
+            // Change jpeg quality {65,75,85} to {normal,fine,superfine}
+            String quality = pref.getString(KEY_JPEG_QUALITY, "85");
+            if (quality.equals("65")) {
+                quality = "normal";
+            } else if (quality.equals("75")) {
+                quality = "fine";
+            } else {
+                quality = "superfine";
+            }
+            editor.putString(KEY_JPEG_QUALITY, quality);
+            version = 2;
+        }
+        if (version == 2) {
+            editor.putString(KEY_RECORD_LOCATION,
+                    pref.getBoolean(KEY_RECORD_LOCATION, false)
+                    ? RecordLocationPreference.VALUE_ON
+                    : RecordLocationPreference.VALUE_NONE);
+            version = 3;
+        }
+        if (version == 3) {
+            // Just use video quality to replace it and
+            // ignore the current settings.
+            editor.remove("pref_camera_videoquality_key");
+            editor.remove("pref_camera_video_duration_key");
+        }
+
+        editor.putInt(KEY_VERSION, CURRENT_VERSION);
+        editor.apply();
+    }
+
+    private static void upgradeCameraId(SharedPreferences pref) {
+        // The id stored in the preference may be out of range if we are running
+        // inside the emulator and a webcam is removed.
+        // Note: This method accesses the global preferences directly, not the
+        // combo preferences.
+        int cameraId = readPreferredCameraId(pref);
+        if (cameraId == 0) return;  // fast path
+
+        int n = CameraHolder.instance().getNumberOfCameras();
+        if (cameraId < 0 || cameraId >= n) {
+            writePreferredCameraId(pref, 0);
+        }
+    }
+
+    public static int readPreferredCameraId(SharedPreferences pref) {
+        return Integer.parseInt(pref.getString(KEY_CAMERA_ID, "0"));
+    }
+
+    public static void writePreferredCameraId(SharedPreferences pref,
+            int cameraId) {
+        Editor editor = pref.edit();
+        editor.putString(KEY_CAMERA_ID, Integer.toString(cameraId));
+        editor.apply();
+    }
+
+    public static int readExposure(ComboPreferences preferences) {
+        String exposure = preferences.getString(
+                CameraSettings.KEY_EXPOSURE,
+                EXPOSURE_DEFAULT_VALUE);
+        try {
+            return Integer.parseInt(exposure);
+        } catch (Exception ex) {
+            Log.e(TAG, "Invalid exposure: " + exposure);
+        }
+        return 0;
+    }
+
+    public static int readEffectType(SharedPreferences pref) {
+        String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
+        if (effectSelection.equals("none")) {
+            return EffectsRecorder.EFFECT_NONE;
+        } else if (effectSelection.startsWith("goofy_face")) {
+            return EffectsRecorder.EFFECT_GOOFY_FACE;
+        } else if (effectSelection.startsWith("backdropper")) {
+            return EffectsRecorder.EFFECT_BACKDROPPER;
+        }
+        Log.e(TAG, "Invalid effect selection: " + effectSelection);
+        return EffectsRecorder.EFFECT_NONE;
+    }
+
+    public static Object readEffectParameter(SharedPreferences pref) {
+        String effectSelection = pref.getString(KEY_VIDEO_EFFECT, "none");
+        if (effectSelection.equals("none")) {
+            return null;
+        }
+        int separatorIndex = effectSelection.indexOf('/');
+        String effectParameter =
+                effectSelection.substring(separatorIndex + 1);
+        if (effectSelection.startsWith("goofy_face")) {
+            if (effectParameter.equals("squeeze")) {
+                return EffectsRecorder.EFFECT_GF_SQUEEZE;
+            } else if (effectParameter.equals("big_eyes")) {
+                return EffectsRecorder.EFFECT_GF_BIG_EYES;
+            } else if (effectParameter.equals("big_mouth")) {
+                return EffectsRecorder.EFFECT_GF_BIG_MOUTH;
+            } else if (effectParameter.equals("small_mouth")) {
+                return EffectsRecorder.EFFECT_GF_SMALL_MOUTH;
+            } else if (effectParameter.equals("big_nose")) {
+                return EffectsRecorder.EFFECT_GF_BIG_NOSE;
+            } else if (effectParameter.equals("small_eyes")) {
+                return EffectsRecorder.EFFECT_GF_SMALL_EYES;
+            }
+        } else if (effectSelection.startsWith("backdropper")) {
+            // Parameter is a string that either encodes the URI to use,
+            // or specifies 'gallery'.
+            return effectParameter;
+        }
+
+        Log.e(TAG, "Invalid effect selection: " + effectSelection);
+        return null;
+    }
+
+    public static void restorePreferences(Context context,
+            ComboPreferences preferences, Parameters parameters) {
+        int currentCameraId = readPreferredCameraId(preferences);
+
+        // Clear the preferences of both cameras.
+        int backCameraId = CameraHolder.instance().getBackCameraId();
+        if (backCameraId != -1) {
+            preferences.setLocalId(context, backCameraId);
+            Editor editor = preferences.edit();
+            editor.clear();
+            editor.apply();
+        }
+        int frontCameraId = CameraHolder.instance().getFrontCameraId();
+        if (frontCameraId != -1) {
+            preferences.setLocalId(context, frontCameraId);
+            Editor editor = preferences.edit();
+            editor.clear();
+            editor.apply();
+        }
+
+        // Switch back to the preferences of the current camera. Otherwise,
+        // we may write the preference to wrong camera later.
+        preferences.setLocalId(context, currentCameraId);
+
+        upgradeGlobalPreferences(preferences.getGlobal());
+        upgradeLocalPreferences(preferences.getLocal());
+
+        // Write back the current camera id because parameters are related to
+        // the camera. Otherwise, we may switch to the front camera but the
+        // initial picture size is that of the back camera.
+        initialCameraPictureSize(context, parameters);
+        writePreferredCameraId(preferences, currentCameraId);
+    }
+
+    private ArrayList<String> getSupportedVideoQuality() {
+        ArrayList<String> supported = new ArrayList<String>();
+        // Check for supported quality
+        if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
+            getFineResolutionQuality(supported);
+        } else {
+            supported.add(Integer.toString(CamcorderProfile.QUALITY_HIGH));
+            CamcorderProfile high = CamcorderProfile.get(
+                    mCameraId, CamcorderProfile.QUALITY_HIGH);
+            CamcorderProfile low = CamcorderProfile.get(
+                    mCameraId, CamcorderProfile.QUALITY_LOW);
+            if (high.videoFrameHeight * high.videoFrameWidth >
+                    low.videoFrameHeight * low.videoFrameWidth) {
+                supported.add(Integer.toString(CamcorderProfile.QUALITY_LOW));
+            }
+        }
+
+        return supported;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private void getFineResolutionQuality(ArrayList<String> supported) {
+        if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_1080P)) {
+            supported.add(Integer.toString(CamcorderProfile.QUALITY_1080P));
+        }
+        if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_720P)) {
+            supported.add(Integer.toString(CamcorderProfile.QUALITY_720P));
+        }
+        if (CamcorderProfile.hasProfile(mCameraId, CamcorderProfile.QUALITY_480P)) {
+            supported.add(Integer.toString(CamcorderProfile.QUALITY_480P));
+        }
+    }
+
+    private void initVideoEffect(PreferenceGroup group, ListPreference videoEffect) {
+        CharSequence[] values = videoEffect.getEntryValues();
+
+        boolean goofyFaceSupported =
+                EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_GOOFY_FACE);
+        boolean backdropperSupported =
+                EffectsRecorder.isEffectSupported(EffectsRecorder.EFFECT_BACKDROPPER) &&
+                Util.isAutoExposureLockSupported(mParameters) &&
+                Util.isAutoWhiteBalanceLockSupported(mParameters);
+
+        ArrayList<String> supported = new ArrayList<String>();
+        for (CharSequence value : values) {
+            String effectSelection = value.toString();
+            if (!goofyFaceSupported && effectSelection.startsWith("goofy_face")) continue;
+            if (!backdropperSupported && effectSelection.startsWith("backdropper")) continue;
+            supported.add(effectSelection);
+        }
+
+        filterUnsupportedOptions(group, videoEffect, supported);
+    }
+}
diff --git a/src/com/android/camera/CaptureAnimManager.java b/src/com/android/camera/CaptureAnimManager.java
new file mode 100644
index 0000000..6e80925
--- /dev/null
+++ b/src/com/android/camera/CaptureAnimManager.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2012 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.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.os.SystemClock;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.NinePatchTexture;
+import com.android.gallery3d.glrenderer.RawTexture;
+
+/**
+ * Class to handle the capture animation.
+ */
+public class CaptureAnimManager {
+    @SuppressWarnings("unused")
+    private static final String TAG = "CAM_Capture";
+    // times mark endpoint of animation phase
+    private static final int TIME_FLASH = 200;
+    private static final int TIME_HOLD = 400;
+    private static final int TIME_SLIDE = 800;
+    private static final int TIME_HOLD2 = 3300;
+    private static final int TIME_SLIDE2 = 4100;
+
+    private static final int ANIM_BOTH = 0;
+    private static final int ANIM_FLASH = 1;
+    private static final int ANIM_SLIDE = 2;
+    private static final int ANIM_HOLD2 = 3;
+    private static final int ANIM_SLIDE2 = 4;
+
+    private final Interpolator mSlideInterpolator = new DecelerateInterpolator();
+
+    private volatile int mAnimOrientation;  // Could be 0, 90, 180 or 270 degrees.
+    private long mAnimStartTime;  // milliseconds.
+    private float mX;  // The center of the whole view including preview and review.
+    private float mY;
+    private int mDrawWidth;
+    private int mDrawHeight;
+    private int mAnimType;
+
+    private int mHoldX;
+    private int mHoldY;
+    private int mHoldW;
+    private int mHoldH;
+
+    private int mOffset;
+
+    private int mMarginRight;
+    private int mMarginTop;
+    private int mSize;
+    private Resources mResources;
+    private NinePatchTexture mBorder;
+    private int mShadowSize;
+
+    public static int getAnimationDuration() {
+        return TIME_SLIDE2;
+    }
+
+    /* preview: camera preview view.
+     * review: view of picture just taken.
+     */
+    public CaptureAnimManager(Context ctx) {
+        mBorder = new NinePatchTexture(ctx, R.drawable.capture_thumbnail_shadow);
+        mResources = ctx.getResources();
+    }
+
+    public void setOrientation(int displayRotation) {
+        mAnimOrientation = (360 - displayRotation) % 360;
+    }
+
+    public void animateSlide() {
+        if (mAnimType != ANIM_FLASH) {
+            return;
+        }
+        mAnimType = ANIM_SLIDE;
+        mAnimStartTime = SystemClock.uptimeMillis();
+    }
+
+    public void animateFlash() {
+        mAnimType = ANIM_FLASH;
+    }
+
+    public void animateFlashAndSlide() {
+        mAnimType = ANIM_BOTH;
+    }
+
+    public void startAnimation() {
+        mAnimStartTime = SystemClock.uptimeMillis();
+    }
+
+    private void setAnimationGeometry(int x, int y, int w, int h) {
+        mMarginRight = mResources.getDimensionPixelSize(R.dimen.capture_margin_right);
+        mMarginTop = mResources.getDimensionPixelSize(R.dimen.capture_margin_top);
+        mSize = mResources.getDimensionPixelSize(R.dimen.capture_size);
+        mShadowSize = mResources.getDimensionPixelSize(R.dimen.capture_border);
+        mOffset = mMarginRight + mSize;
+        // Set the views to the initial positions.
+        mDrawWidth = w;
+        mDrawHeight = h;
+        mX = x;
+        mY = y;
+        mHoldW = mSize;
+        mHoldH = mSize;
+        switch (mAnimOrientation) {
+            case 0:  // Preview is on the left.
+                mHoldX = x + w - mMarginRight - mSize;
+                mHoldY = y + mMarginTop;
+                break;
+            case 90:  // Preview is below.
+                mHoldX = x + mMarginTop;
+                mHoldY = y + mMarginRight;
+                break;
+            case 180:  // Preview on the right.
+                mHoldX = x + mMarginRight;
+                mHoldY = y + h - mMarginTop - mSize;
+                break;
+            case 270:  // Preview is above.
+                mHoldX = x + w - mMarginTop - mSize;
+                mHoldY = y + h - mMarginRight - mSize;
+                break;
+        }
+    }
+
+    // Returns true if the animation has been drawn.
+    public boolean drawAnimation(GLCanvas canvas, CameraScreenNail preview,
+                RawTexture review, int lx, int ly, int lw, int lh) {
+        setAnimationGeometry(lx, ly, lw, lh);
+        long timeDiff = SystemClock.uptimeMillis() - mAnimStartTime;
+        // Check if the animation is over
+        if (mAnimType == ANIM_SLIDE && timeDiff > TIME_SLIDE2 - TIME_HOLD) return false;
+        if (mAnimType == ANIM_BOTH && timeDiff > TIME_SLIDE2) return false;
+
+        // determine phase and time in phase
+        int animStep = mAnimType;
+        if (mAnimType == ANIM_SLIDE) {
+            timeDiff += TIME_HOLD;
+        }
+        if (mAnimType == ANIM_SLIDE || mAnimType == ANIM_BOTH) {
+            if (timeDiff < TIME_HOLD) {
+                animStep = ANIM_FLASH;
+            } else if (timeDiff < TIME_SLIDE) {
+                animStep = ANIM_SLIDE;
+                timeDiff -= TIME_HOLD;
+            } else if (timeDiff < TIME_HOLD2) {
+                animStep = ANIM_HOLD2;
+                timeDiff -= TIME_SLIDE;
+            } else {
+                // SLIDE2
+                animStep = ANIM_SLIDE2;
+                timeDiff -= TIME_HOLD2;
+            }
+        }
+
+        if (animStep == ANIM_FLASH) {
+            review.draw(canvas, (int) mX, (int) mY, mDrawWidth, mDrawHeight);
+            if (timeDiff < TIME_FLASH) {
+                float f = 0.3f - 0.3f * timeDiff / TIME_FLASH;
+                int color = Color.argb((int) (255 * f), 255, 255, 255);
+                canvas.fillRect(mX, mY, mDrawWidth, mDrawHeight, color);
+            }
+        } else if (animStep == ANIM_SLIDE) {
+            float fraction = mSlideInterpolator.getInterpolation((float) (timeDiff) / (TIME_SLIDE - TIME_HOLD));
+            float x = mX;
+            float y = mY;
+            float w = 0;
+            float h = 0;
+            x = interpolate(mX, mHoldX, fraction);
+            y = interpolate(mY, mHoldY, fraction);
+            w = interpolate(mDrawWidth, mHoldW, fraction);
+            h = interpolate(mDrawHeight, mHoldH, fraction);
+            preview.directDraw(canvas, (int) mX, (int) mY, mDrawWidth, mDrawHeight);
+            review.draw(canvas, (int) x, (int) y, (int) w, (int) h);
+        } else if (animStep == ANIM_HOLD2) {
+            preview.directDraw(canvas, (int) mX, (int) mY, mDrawWidth, mDrawHeight);
+            review.draw(canvas, mHoldX, mHoldY, mHoldW, mHoldH);
+            mBorder.draw(canvas, (int) mHoldX - mShadowSize, (int) mHoldY - mShadowSize,
+                    (int) mHoldW + 2 * mShadowSize, (int) mHoldH + 2 * mShadowSize);
+        } else if (animStep == ANIM_SLIDE2) {
+            float fraction = (float)(timeDiff) / (TIME_SLIDE2 - TIME_HOLD2);
+            float x = mHoldX;
+            float y = mHoldY;
+            float d = mOffset * fraction;
+            switch (mAnimOrientation) {
+            case 0:
+                x = mHoldX + d;
+                break;
+            case 180:
+                x = mHoldX - d;
+                break;
+            case 90:
+                y = mHoldY - d;
+                break;
+            case 270:
+                y = mHoldY + d;
+                break;
+            }
+            preview.directDraw(canvas, (int) mX, (int) mY, mDrawWidth, mDrawHeight);
+            mBorder.draw(canvas, (int) x - mShadowSize, (int) y - mShadowSize,
+                    (int) mHoldW + 2 * mShadowSize, (int) mHoldH + 2 * mShadowSize);
+            review.draw(canvas, (int) x, (int) y, mHoldW, mHoldH);
+        }
+        return true;
+    }
+
+    private static float interpolate(float start, float end, float fraction) {
+        return start + (end - start) * fraction;
+    }
+
+}
diff --git a/src/com/android/camera/ComboPreferences.java b/src/com/android/camera/ComboPreferences.java
new file mode 100644
index 0000000..e17e47a
--- /dev/null
+++ b/src/com/android/camera/ComboPreferences.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2010 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.app.backup.BackupManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.preference.PreferenceManager;
+
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class ComboPreferences implements
+        SharedPreferences,
+        OnSharedPreferenceChangeListener {
+    private SharedPreferences mPrefGlobal;  // global preferences
+    private SharedPreferences mPrefLocal;  // per-camera preferences
+    private String mPackageName;
+    private CopyOnWriteArrayList<OnSharedPreferenceChangeListener> mListeners;
+    // TODO: Remove this WeakHashMap in the camera code refactoring
+    private static WeakHashMap<Context, ComboPreferences> sMap =
+            new WeakHashMap<Context, ComboPreferences>();
+
+    public ComboPreferences(Context context) {
+        mPackageName = context.getPackageName();
+        mPrefGlobal = context.getSharedPreferences(
+                getGlobalSharedPreferencesName(context), Context.MODE_PRIVATE);
+        mPrefGlobal.registerOnSharedPreferenceChangeListener(this);
+
+        synchronized (sMap) {
+            sMap.put(context, this);
+        }
+        mListeners = new CopyOnWriteArrayList<OnSharedPreferenceChangeListener>();
+
+        // The global preferences was previously stored in the default
+        // shared preferences file. They should be stored in the camera-specific
+        // shared preferences file so we can backup them solely.
+        SharedPreferences oldprefs =
+                PreferenceManager.getDefaultSharedPreferences(context);
+        if (!mPrefGlobal.contains(CameraSettings.KEY_VERSION)
+                && oldprefs.contains(CameraSettings.KEY_VERSION)) {
+            moveGlobalPrefsFrom(oldprefs);
+        }
+    }
+
+    public static ComboPreferences get(Context context) {
+        synchronized (sMap) {
+            return sMap.get(context);
+        }
+    }
+
+    private static String getLocalSharedPreferencesName(
+            Context context, int cameraId) {
+        return context.getPackageName() + "_preferences_" + cameraId;
+    }
+
+    private static String getGlobalSharedPreferencesName(Context context) {
+        return context.getPackageName() + "_preferences_camera";
+    }
+
+    private void movePrefFrom(
+            Map<String, ?> m, String key, SharedPreferences src) {
+        if (m.containsKey(key)) {
+            Object v = m.get(key);
+            if (v instanceof String) {
+                mPrefGlobal.edit().putString(key, (String) v).apply();
+            } else if (v instanceof Integer) {
+                mPrefGlobal.edit().putInt(key, (Integer) v).apply();
+            } else if (v instanceof Long) {
+                mPrefGlobal.edit().putLong(key, (Long) v).apply();
+            } else if (v instanceof Float) {
+                mPrefGlobal.edit().putFloat(key, (Float) v).apply();
+            } else if (v instanceof Boolean) {
+                mPrefGlobal.edit().putBoolean(key, (Boolean) v).apply();
+            }
+            src.edit().remove(key).apply();
+        }
+    }
+
+    private void moveGlobalPrefsFrom(SharedPreferences src) {
+        Map<String, ?> prefMap = src.getAll();
+        movePrefFrom(prefMap, CameraSettings.KEY_VERSION, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_CAMERA_ID, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_RECORD_LOCATION, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, src);
+        movePrefFrom(prefMap, CameraSettings.KEY_VIDEO_EFFECT, src);
+    }
+
+    public static String[] getSharedPreferencesNames(Context context) {
+        int numOfCameras = CameraHolder.instance().getNumberOfCameras();
+        String prefNames[] = new String[numOfCameras + 1];
+        prefNames[0] = getGlobalSharedPreferencesName(context);
+        for (int i = 0; i < numOfCameras; i++) {
+            prefNames[i + 1] = getLocalSharedPreferencesName(context, i);
+        }
+        return prefNames;
+    }
+
+    // Sets the camera id and reads its preferences. Each camera has its own
+    // preferences.
+    public void setLocalId(Context context, int cameraId) {
+        String prefName = getLocalSharedPreferencesName(context, cameraId);
+        if (mPrefLocal != null) {
+            mPrefLocal.unregisterOnSharedPreferenceChangeListener(this);
+        }
+        mPrefLocal = context.getSharedPreferences(
+                prefName, Context.MODE_PRIVATE);
+        mPrefLocal.registerOnSharedPreferenceChangeListener(this);
+    }
+
+    public SharedPreferences getGlobal() {
+        return mPrefGlobal;
+    }
+
+    public SharedPreferences getLocal() {
+        return mPrefLocal;
+    }
+
+    @Override
+    public Map<String, ?> getAll() {
+        throw new UnsupportedOperationException(); // Can be implemented if needed.
+    }
+
+    private static boolean isGlobal(String key) {
+        return key.equals(CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL)
+                || key.equals(CameraSettings.KEY_CAMERA_ID)
+                || key.equals(CameraSettings.KEY_RECORD_LOCATION)
+                || key.equals(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN)
+                || key.equals(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN)
+                || key.equals(CameraSettings.KEY_VIDEO_EFFECT)
+                || key.equals(CameraSettings.KEY_TIMER)
+                || key.equals(CameraSettings.KEY_TIMER_SOUND_EFFECTS)
+                || key.equals(CameraSettings.KEY_PHOTOSPHERE_PICTURESIZE);
+    }
+
+    @Override
+    public String getString(String key, String defValue) {
+        if (isGlobal(key) || !mPrefLocal.contains(key)) {
+            return mPrefGlobal.getString(key, defValue);
+        } else {
+            return mPrefLocal.getString(key, defValue);
+        }
+    }
+
+    @Override
+    public int getInt(String key, int defValue) {
+        if (isGlobal(key) || !mPrefLocal.contains(key)) {
+            return mPrefGlobal.getInt(key, defValue);
+        } else {
+            return mPrefLocal.getInt(key, defValue);
+        }
+    }
+
+    @Override
+    public long getLong(String key, long defValue) {
+        if (isGlobal(key) || !mPrefLocal.contains(key)) {
+            return mPrefGlobal.getLong(key, defValue);
+        } else {
+            return mPrefLocal.getLong(key, defValue);
+        }
+    }
+
+    @Override
+    public float getFloat(String key, float defValue) {
+        if (isGlobal(key) || !mPrefLocal.contains(key)) {
+            return mPrefGlobal.getFloat(key, defValue);
+        } else {
+            return mPrefLocal.getFloat(key, defValue);
+        }
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defValue) {
+        if (isGlobal(key) || !mPrefLocal.contains(key)) {
+            return mPrefGlobal.getBoolean(key, defValue);
+        } else {
+            return mPrefLocal.getBoolean(key, defValue);
+        }
+    }
+
+    // This method is not used.
+    @Override
+    public Set<String> getStringSet(String key, Set<String> defValues) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean contains(String key) {
+        return mPrefLocal.contains(key) || mPrefGlobal.contains(key);
+    }
+
+    private class MyEditor implements Editor {
+        private Editor mEditorGlobal;
+        private Editor mEditorLocal;
+
+        MyEditor() {
+            mEditorGlobal = mPrefGlobal.edit();
+            mEditorLocal = mPrefLocal.edit();
+        }
+
+        @Override
+        public boolean commit() {
+            boolean result1 = mEditorGlobal.commit();
+            boolean result2 = mEditorLocal.commit();
+            return result1 && result2;
+        }
+
+        @Override
+        public void apply() {
+            mEditorGlobal.apply();
+            mEditorLocal.apply();
+        }
+
+        // Note: clear() and remove() affects both local and global preferences.
+        @Override
+        public Editor clear() {
+            mEditorGlobal.clear();
+            mEditorLocal.clear();
+            return this;
+        }
+
+        @Override
+        public Editor remove(String key) {
+            mEditorGlobal.remove(key);
+            mEditorLocal.remove(key);
+            return this;
+        }
+
+        @Override
+        public Editor putString(String key, String value) {
+            if (isGlobal(key)) {
+                mEditorGlobal.putString(key, value);
+            } else {
+                mEditorLocal.putString(key, value);
+            }
+            return this;
+        }
+
+        @Override
+        public Editor putInt(String key, int value) {
+            if (isGlobal(key)) {
+                mEditorGlobal.putInt(key, value);
+            } else {
+                mEditorLocal.putInt(key, value);
+            }
+            return this;
+        }
+
+        @Override
+        public Editor putLong(String key, long value) {
+            if (isGlobal(key)) {
+                mEditorGlobal.putLong(key, value);
+            } else {
+                mEditorLocal.putLong(key, value);
+            }
+            return this;
+        }
+
+        @Override
+        public Editor putFloat(String key, float value) {
+            if (isGlobal(key)) {
+                mEditorGlobal.putFloat(key, value);
+            } else {
+                mEditorLocal.putFloat(key, value);
+            }
+            return this;
+        }
+
+        @Override
+        public Editor putBoolean(String key, boolean value) {
+            if (isGlobal(key)) {
+                mEditorGlobal.putBoolean(key, value);
+            } else {
+                mEditorLocal.putBoolean(key, value);
+            }
+            return this;
+        }
+
+        // This method is not used.
+        @Override
+        public Editor putStringSet(String key, Set<String> values) {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    // Note the remove() and clear() of the returned Editor may not work as
+    // expected because it doesn't touch the global preferences at all.
+    @Override
+    public Editor edit() {
+        return new MyEditor();
+    }
+
+    @Override
+    public void registerOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void unregisterOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+            String key) {
+        for (OnSharedPreferenceChangeListener listener : mListeners) {
+            listener.onSharedPreferenceChanged(this, key);
+        }
+        BackupManager.dataChanged(mPackageName);
+        UsageStatistics.onEvent("CameraSettingsChange", null, key);
+    }
+}
diff --git a/src/com/android/camera/CountDownTimerPreference.java b/src/com/android/camera/CountDownTimerPreference.java
new file mode 100644
index 0000000..9c66dda
--- /dev/null
+++ b/src/com/android/camera/CountDownTimerPreference.java
@@ -0,0 +1,48 @@
+/*
+ * 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.Context;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.R;
+
+public class CountDownTimerPreference extends ListPreference {
+    private static final int[] DURATIONS = {
+        0, 1, 2, 3, 4, 5, 10, 15, 20, 30, 60
+    };
+    public CountDownTimerPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initCountDownDurationChoices(context);
+    }
+
+    private void initCountDownDurationChoices(Context context) {
+        CharSequence[] entryValues = new CharSequence[DURATIONS.length];
+        CharSequence[] entries = new CharSequence[DURATIONS.length];
+        for (int i = 0; i < DURATIONS.length; i++) {
+            entryValues[i] = Integer.toString(DURATIONS[i]);
+            if (i == 0) {
+                entries[0] = context.getString(R.string.setting_off); // Off
+            } else {
+                entries[i] = context.getResources()
+                        .getQuantityString(R.plurals.pref_camera_timer_entry, i, i);
+            }
+        }
+        setEntries(entries);
+        setEntryValues(entryValues);
+    }
+}
diff --git a/src/com/android/camera/DisableCameraReceiver.java b/src/com/android/camera/DisableCameraReceiver.java
new file mode 100644
index 0000000..3517405
--- /dev/null
+++ b/src/com/android/camera/DisableCameraReceiver.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2012 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.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.Camera.CameraInfo;
+import android.util.Log;
+
+// We want to disable camera-related activities if there is no camera. This
+// receiver runs when BOOT_COMPLETED intent is received. After running once
+// this receiver will be disabled, so it will not run again.
+public class DisableCameraReceiver extends BroadcastReceiver {
+    private static final String TAG = "DisableCameraReceiver";
+    private static final boolean CHECK_BACK_CAMERA_ONLY = true;
+    private static final String ACTIVITIES[] = {
+        "com.android.camera.CameraLauncher",
+    };
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Disable camera-related activities if there is no camera.
+        boolean needCameraActivity = CHECK_BACK_CAMERA_ONLY
+            ? hasBackCamera()
+            : hasCamera();
+
+        if (!needCameraActivity) {
+            Log.i(TAG, "disable all camera activities");
+            for (int i = 0; i < ACTIVITIES.length; i++) {
+                disableComponent(context, ACTIVITIES[i]);
+            }
+        }
+
+        // Disable this receiver so it won't run again.
+        disableComponent(context, "com.android.camera.DisableCameraReceiver");
+    }
+
+    private boolean hasCamera() {
+        int n = android.hardware.Camera.getNumberOfCameras();
+        Log.i(TAG, "number of camera: " + n);
+        return (n > 0);
+    }
+
+    private boolean hasBackCamera() {
+        int n = android.hardware.Camera.getNumberOfCameras();
+        CameraInfo info = new CameraInfo();
+        for (int i = 0; i < n; i++) {
+            android.hardware.Camera.getCameraInfo(i, info);
+            if (info.facing == CameraInfo.CAMERA_FACING_BACK) {
+                Log.i(TAG, "back camera found: " + i);
+                return true;
+            }
+        }
+        Log.i(TAG, "no back camera");
+        return false;
+    }
+
+    private void disableComponent(Context context, String klass) {
+        ComponentName name = new ComponentName(context, klass);
+        PackageManager pm = context.getPackageManager();
+
+        // We need the DONT_KILL_APP flag, otherwise we will be killed
+        // immediately because we are in the same app.
+        pm.setComponentEnabledSetting(name,
+            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+            PackageManager.DONT_KILL_APP);
+    }
+}
diff --git a/src/com/android/camera/EffectsRecorder.java b/src/com/android/camera/EffectsRecorder.java
new file mode 100644
index 0000000..2db44c7
--- /dev/null
+++ b/src/com/android/camera/EffectsRecorder.java
@@ -0,0 +1,1240 @@
+/*
+ * Copyright (C) 2011 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.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+
+/**
+ * Encapsulates the mobile filter framework components needed to record video
+ * with effects applied. Modeled after MediaRecorder.
+ */
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
+public class EffectsRecorder {
+    private static final String TAG = "EffectsRecorder";
+
+    private static Class<?> sClassFilter;
+    private static Method sFilterIsAvailable;
+    private static EffectsRecorder sEffectsRecorder;
+    // The index of the current effects recorder.
+    private static int sEffectsRecorderIndex;
+
+    private static boolean sReflectionInited = false;
+
+    private static Class<?> sClsLearningDoneListener;
+    private static Class<?> sClsOnRunnerDoneListener;
+    private static Class<?> sClsOnRecordingDoneListener;
+    private static Class<?> sClsSurfaceTextureSourceListener;
+
+    private static Method sFilterSetInputValue;
+
+    private static Constructor<?> sCtPoint;
+    private static Constructor<?> sCtQuad;
+
+    private static Method sLearningDoneListenerOnLearningDone;
+
+    private static Method sObjectEquals;
+    private static Method sObjectToString;
+
+    private static Class<?> sClsGraphRunner;
+    private static Method sGraphRunnerGetGraph;
+    private static Method sGraphRunnerSetDoneCallback;
+    private static Method sGraphRunnerRun;
+    private static Method sGraphRunnerGetError;
+    private static Method sGraphRunnerStop;
+
+    private static Method sFilterGraphGetFilter;
+    private static Method sFilterGraphTearDown;
+
+    private static Method sOnRunnerDoneListenerOnRunnerDone;
+
+    private static Class<?> sClsGraphEnvironment;
+    private static Constructor<?> sCtGraphEnvironment;
+    private static Method sGraphEnvironmentCreateGLEnvironment;
+    private static Method sGraphEnvironmentGetRunner;
+    private static Method sGraphEnvironmentAddReferences;
+    private static Method sGraphEnvironmentLoadGraph;
+    private static Method sGraphEnvironmentGetContext;
+
+    private static Method sFilterContextGetGLEnvironment;
+    private static Method sGLEnvironmentIsActive;
+    private static Method sGLEnvironmentActivate;
+    private static Method sGLEnvironmentDeactivate;
+    private static Method sSurfaceTextureTargetDisconnect;
+    private static Method sOnRecordingDoneListenerOnRecordingDone;
+    private static Method sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady;
+
+    private Object mLearningDoneListener;
+    private Object mRunnerDoneCallback;
+    private Object mSourceReadyCallback;
+    // A callback to finalize the media after the recording is done.
+    private Object mRecordingDoneListener;
+
+    static {
+        try {
+            sClassFilter = Class.forName("android.filterfw.core.Filter");
+            sFilterIsAvailable = sClassFilter.getMethod("isAvailable",
+                    String.class);
+        } catch (ClassNotFoundException ex) {
+            Log.v(TAG, "Can't find the class android.filterfw.core.Filter");
+        } catch (NoSuchMethodException e) {
+            Log.v(TAG, "Can't find the method Filter.isAvailable");
+        }
+    }
+
+    public static final int  EFFECT_NONE        = 0;
+    public static final int  EFFECT_GOOFY_FACE  = 1;
+    public static final int  EFFECT_BACKDROPPER = 2;
+
+    public static final int  EFFECT_GF_SQUEEZE     = 0;
+    public static final int  EFFECT_GF_BIG_EYES    = 1;
+    public static final int  EFFECT_GF_BIG_MOUTH   = 2;
+    public static final int  EFFECT_GF_SMALL_MOUTH = 3;
+    public static final int  EFFECT_GF_BIG_NOSE    = 4;
+    public static final int  EFFECT_GF_SMALL_EYES  = 5;
+    public static final int  NUM_OF_GF_EFFECTS = EFFECT_GF_SMALL_EYES + 1;
+
+    public static final int  EFFECT_MSG_STARTED_LEARNING = 0;
+    public static final int  EFFECT_MSG_DONE_LEARNING    = 1;
+    public static final int  EFFECT_MSG_SWITCHING_EFFECT = 2;
+    public static final int  EFFECT_MSG_EFFECTS_STOPPED  = 3;
+    public static final int  EFFECT_MSG_RECORDING_DONE   = 4;
+    public static final int  EFFECT_MSG_PREVIEW_RUNNING  = 5;
+
+    private Context mContext;
+    private Handler mHandler;
+
+    private CameraManager.CameraProxy mCameraDevice;
+    private CamcorderProfile mProfile;
+    private double mCaptureRate = 0;
+    private SurfaceTexture mPreviewSurfaceTexture;
+    private int mPreviewWidth;
+    private int mPreviewHeight;
+    private MediaRecorder.OnInfoListener mInfoListener;
+    private MediaRecorder.OnErrorListener mErrorListener;
+
+    private String mOutputFile;
+    private FileDescriptor mFd;
+    private int mOrientationHint = 0;
+    private long mMaxFileSize = 0;
+    private int mMaxDurationMs = 0;
+    private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
+    private int mCameraDisplayOrientation;
+
+    private int mEffect = EFFECT_NONE;
+    private int mCurrentEffect = EFFECT_NONE;
+    private EffectsListener mEffectsListener;
+
+    private Object mEffectParameter;
+
+    private Object mGraphEnv;
+    private int mGraphId;
+    private Object mRunner = null;
+    private Object mOldRunner = null;
+
+    private SurfaceTexture mTextureSource;
+
+    private static final int STATE_CONFIGURE              = 0;
+    private static final int STATE_WAITING_FOR_SURFACE    = 1;
+    private static final int STATE_STARTING_PREVIEW       = 2;
+    private static final int STATE_PREVIEW                = 3;
+    private static final int STATE_RECORD                 = 4;
+    private static final int STATE_RELEASED               = 5;
+    private int mState = STATE_CONFIGURE;
+
+    private boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
+    private SoundClips.Player mSoundPlayer;
+
+    /** Determine if a given effect is supported at runtime
+     * Some effects require libraries not available on all devices
+     */
+    public static boolean isEffectSupported(int effectId) {
+        if (sFilterIsAvailable == null)  return false;
+
+        try {
+            switch (effectId) {
+                case EFFECT_GOOFY_FACE:
+                    return (Boolean) sFilterIsAvailable.invoke(null,
+                            "com.google.android.filterpacks.facedetect.GoofyRenderFilter");
+                case EFFECT_BACKDROPPER:
+                    return (Boolean) sFilterIsAvailable.invoke(null,
+                            "android.filterpacks.videoproc.BackDropperFilter");
+                default:
+                    return false;
+            }
+        } catch (Exception ex) {
+            Log.e(TAG, "Fail to check filter", ex);
+        }
+        return false;
+    }
+
+    public EffectsRecorder(Context context) {
+        if (mLogVerbose) Log.v(TAG, "EffectsRecorder created (" + this + ")");
+
+        if (!sReflectionInited) {
+            try {
+                sFilterSetInputValue = sClassFilter.getMethod("setInputValue",
+                        new Class[] {String.class, Object.class});
+
+                Class<?> clsPoint = Class.forName("android.filterfw.geometry.Point");
+                sCtPoint = clsPoint.getConstructor(new Class[] {float.class,
+                        float.class});
+
+                Class<?> clsQuad = Class.forName("android.filterfw.geometry.Quad");
+                sCtQuad = clsQuad.getConstructor(new Class[] {clsPoint, clsPoint,
+                        clsPoint, clsPoint});
+
+                Class<?> clsBackDropperFilter = Class.forName(
+                        "android.filterpacks.videoproc.BackDropperFilter");
+                sClsLearningDoneListener = Class.forName(
+                        "android.filterpacks.videoproc.BackDropperFilter$LearningDoneListener");
+                sLearningDoneListenerOnLearningDone = sClsLearningDoneListener
+                        .getMethod("onLearningDone", new Class[] {clsBackDropperFilter});
+
+                sObjectEquals = Object.class.getMethod("equals", new Class[] {Object.class});
+                sObjectToString = Object.class.getMethod("toString");
+
+                sClsOnRunnerDoneListener = Class.forName(
+                        "android.filterfw.core.GraphRunner$OnRunnerDoneListener");
+                sOnRunnerDoneListenerOnRunnerDone = sClsOnRunnerDoneListener.getMethod(
+                        "onRunnerDone", new Class[] {int.class});
+
+                sClsGraphRunner = Class.forName("android.filterfw.core.GraphRunner");
+                sGraphRunnerGetGraph = sClsGraphRunner.getMethod("getGraph");
+                sGraphRunnerSetDoneCallback = sClsGraphRunner.getMethod(
+                        "setDoneCallback", new Class[] {sClsOnRunnerDoneListener});
+                sGraphRunnerRun = sClsGraphRunner.getMethod("run");
+                sGraphRunnerGetError = sClsGraphRunner.getMethod("getError");
+                sGraphRunnerStop = sClsGraphRunner.getMethod("stop");
+
+                Class<?> clsFilterContext = Class.forName("android.filterfw.core.FilterContext");
+                sFilterContextGetGLEnvironment = clsFilterContext.getMethod(
+                        "getGLEnvironment");
+
+                Class<?> clsFilterGraph = Class.forName("android.filterfw.core.FilterGraph");
+                sFilterGraphGetFilter = clsFilterGraph.getMethod("getFilter",
+                        new Class[] {String.class});
+                sFilterGraphTearDown = clsFilterGraph.getMethod("tearDown",
+                        new Class[] {clsFilterContext});
+
+                sClsGraphEnvironment = Class.forName("android.filterfw.GraphEnvironment");
+                sCtGraphEnvironment = sClsGraphEnvironment.getConstructor();
+                sGraphEnvironmentCreateGLEnvironment = sClsGraphEnvironment.getMethod(
+                        "createGLEnvironment");
+                sGraphEnvironmentGetRunner = sClsGraphEnvironment.getMethod(
+                        "getRunner", new Class[] {int.class, int.class});
+                sGraphEnvironmentAddReferences = sClsGraphEnvironment.getMethod(
+                        "addReferences", new Class[] {Object[].class});
+                sGraphEnvironmentLoadGraph = sClsGraphEnvironment.getMethod(
+                        "loadGraph", new Class[] {Context.class, int.class});
+                sGraphEnvironmentGetContext = sClsGraphEnvironment.getMethod(
+                        "getContext");
+
+                Class<?> clsGLEnvironment = Class.forName("android.filterfw.core.GLEnvironment");
+                sGLEnvironmentIsActive = clsGLEnvironment.getMethod("isActive");
+                sGLEnvironmentActivate = clsGLEnvironment.getMethod("activate");
+                sGLEnvironmentDeactivate = clsGLEnvironment.getMethod("deactivate");
+
+                Class<?> clsSurfaceTextureTarget = Class.forName(
+                        "android.filterpacks.videosrc.SurfaceTextureTarget");
+                sSurfaceTextureTargetDisconnect = clsSurfaceTextureTarget.getMethod(
+                        "disconnect", new Class[] {clsFilterContext});
+
+                sClsOnRecordingDoneListener = Class.forName(
+                        "android.filterpacks.videosink.MediaEncoderFilter$OnRecordingDoneListener");
+                sOnRecordingDoneListenerOnRecordingDone =
+                        sClsOnRecordingDoneListener.getMethod("onRecordingDone");
+
+                sClsSurfaceTextureSourceListener = Class.forName(
+                        "android.filterpacks.videosrc.SurfaceTextureSource$SurfaceTextureSourceListener");
+                sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady =
+                        sClsSurfaceTextureSourceListener.getMethod(
+                                "onSurfaceTextureSourceReady",
+                                new Class[] {SurfaceTexture.class});
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+
+            sReflectionInited = true;
+        }
+
+        sEffectsRecorderIndex++;
+        Log.v(TAG, "Current effects recorder index is " + sEffectsRecorderIndex);
+        sEffectsRecorder = this;
+        SerializableInvocationHandler sih = new SerializableInvocationHandler(
+                sEffectsRecorderIndex);
+        mLearningDoneListener = Proxy.newProxyInstance(
+                sClsLearningDoneListener.getClassLoader(),
+                new Class[] {sClsLearningDoneListener}, sih);
+        mRunnerDoneCallback = Proxy.newProxyInstance(
+                sClsOnRunnerDoneListener.getClassLoader(),
+                new Class[] {sClsOnRunnerDoneListener}, sih);
+        mSourceReadyCallback = Proxy.newProxyInstance(
+                sClsSurfaceTextureSourceListener.getClassLoader(),
+                new Class[] {sClsSurfaceTextureSourceListener}, sih);
+        mRecordingDoneListener =  Proxy.newProxyInstance(
+                sClsOnRecordingDoneListener.getClassLoader(),
+                new Class[] {sClsOnRecordingDoneListener}, sih);
+
+        mContext = context;
+        mHandler = new Handler(Looper.getMainLooper());
+        mSoundPlayer = SoundClips.getPlayer(context);
+    }
+
+    public synchronized void setCamera(CameraManager.CameraProxy cameraDevice) {
+        switch (mState) {
+            case STATE_PREVIEW:
+                throw new RuntimeException("setCamera cannot be called while previewing!");
+            case STATE_RECORD:
+                throw new RuntimeException("setCamera cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setCamera called on an already released recorder!");
+            default:
+                break;
+        }
+
+        mCameraDevice = cameraDevice;
+    }
+
+    public void setProfile(CamcorderProfile profile) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setProfile cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setProfile called on an already released recorder!");
+            default:
+                break;
+        }
+        mProfile = profile;
+    }
+
+    public void setOutputFile(String outputFile) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setOutputFile cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setOutputFile called on an already released recorder!");
+            default:
+                break;
+        }
+
+        mOutputFile = outputFile;
+        mFd = null;
+    }
+
+    public void setOutputFile(FileDescriptor fd) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setOutputFile cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setOutputFile called on an already released recorder!");
+            default:
+                break;
+        }
+
+        mOutputFile = null;
+        mFd = fd;
+    }
+
+    /**
+     * Sets the maximum filesize (in bytes) of the recording session.
+     * This will be passed on to the MediaEncoderFilter and then to the
+     * MediaRecorder ultimately. If zero or negative, the MediaRecorder will
+     * disable the limit
+    */
+    public synchronized void setMaxFileSize(long maxFileSize) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setMaxFileSize cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setMaxFileSize called on an already released recorder!");
+            default:
+                break;
+        }
+        mMaxFileSize = maxFileSize;
+    }
+
+    /**
+    * Sets the maximum recording duration (in ms) for the next recording session
+    * Setting it to zero (the default) disables the limit.
+    */
+    public synchronized void setMaxDuration(int maxDurationMs) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setMaxDuration cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setMaxDuration called on an already released recorder!");
+            default:
+                break;
+        }
+        mMaxDurationMs = maxDurationMs;
+    }
+
+
+    public void setCaptureRate(double fps) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setCaptureRate cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setCaptureRate called on an already released recorder!");
+            default:
+                break;
+        }
+
+        if (mLogVerbose) Log.v(TAG, "Setting time lapse capture rate to " + fps + " fps");
+        mCaptureRate = fps;
+    }
+
+    public void setPreviewSurfaceTexture(SurfaceTexture previewSurfaceTexture,
+                                  int previewWidth,
+                                  int previewHeight) {
+        if (mLogVerbose) Log.v(TAG, "setPreviewSurfaceTexture(" + this + ")");
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException(
+                    "setPreviewSurfaceTexture cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setPreviewSurfaceTexture called on an already released recorder!");
+            default:
+                break;
+        }
+
+        mPreviewSurfaceTexture = previewSurfaceTexture;
+        mPreviewWidth = previewWidth;
+        mPreviewHeight = previewHeight;
+
+        switch (mState) {
+            case STATE_WAITING_FOR_SURFACE:
+                startPreview();
+                break;
+            case STATE_STARTING_PREVIEW:
+            case STATE_PREVIEW:
+                initializeEffect(true);
+                break;
+        }
+    }
+
+    public void setEffect(int effect, Object effectParameter) {
+        if (mLogVerbose) Log.v(TAG,
+                               "setEffect: effect ID " + effect +
+                               ", parameter " + effectParameter.toString());
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setEffect cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setEffect called on an already released recorder!");
+            default:
+                break;
+        }
+
+        mEffect = effect;
+        mEffectParameter = effectParameter;
+
+        if (mState == STATE_PREVIEW ||
+                mState == STATE_STARTING_PREVIEW) {
+            initializeEffect(false);
+        }
+    }
+
+    public interface EffectsListener {
+        public void onEffectsUpdate(int effectId, int effectMsg);
+        public void onEffectsError(Exception exception, String filePath);
+    }
+
+    public void setEffectsListener(EffectsListener listener) {
+        mEffectsListener = listener;
+    }
+
+    private void setFaceDetectOrientation() {
+        if (mCurrentEffect == EFFECT_GOOFY_FACE) {
+            Object rotateFilter = getGraphFilter(mRunner, "rotate");
+            Object metaRotateFilter = getGraphFilter(mRunner, "metarotate");
+            setInputValue(rotateFilter, "rotation", mOrientationHint);
+            int reverseDegrees = (360 - mOrientationHint) % 360;
+            setInputValue(metaRotateFilter, "rotation", reverseDegrees);
+        }
+    }
+
+    private void setRecordingOrientation() {
+        if (mState != STATE_RECORD && mRunner != null) {
+            Object bl = newInstance(sCtPoint, new Object[] {0, 0});
+            Object br = newInstance(sCtPoint, new Object[] {1, 0});
+            Object tl = newInstance(sCtPoint, new Object[] {0, 1});
+            Object tr = newInstance(sCtPoint, new Object[] {1, 1});
+            Object recordingRegion;
+            if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
+                // The back camera is not mirrored, so use a identity transform
+                recordingRegion = newInstance(sCtQuad, new Object[] {bl, br, tl, tr});
+            } else {
+                // Recording region needs to be tweaked for front cameras, since they
+                // mirror their preview
+                if (mOrientationHint == 0 || mOrientationHint == 180) {
+                    // Horizontal flip in landscape
+                    recordingRegion = newInstance(sCtQuad, new Object[] {br, bl, tr, tl});
+                } else {
+                    // Horizontal flip in portrait
+                    recordingRegion = newInstance(sCtQuad, new Object[] {tl, tr, bl, br});
+                }
+            }
+            Object recorder = getGraphFilter(mRunner, "recorder");
+            setInputValue(recorder, "inputRegion", recordingRegion);
+        }
+    }
+    public void setOrientationHint(int degrees) {
+        switch (mState) {
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                        "setOrientationHint called on an already released recorder!");
+            default:
+                break;
+        }
+        if (mLogVerbose) Log.v(TAG, "Setting orientation hint to: " + degrees);
+        mOrientationHint = degrees;
+        setFaceDetectOrientation();
+        setRecordingOrientation();
+    }
+
+    public void setCameraDisplayOrientation(int orientation) {
+        if (mState != STATE_CONFIGURE) {
+            throw new RuntimeException(
+                "setCameraDisplayOrientation called after configuration!");
+        }
+        mCameraDisplayOrientation = orientation;
+    }
+
+    public void setCameraFacing(int facing) {
+        switch (mState) {
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setCameraFacing called on alrady released recorder!");
+            default:
+                break;
+        }
+        mCameraFacing = facing;
+        setRecordingOrientation();
+    }
+
+    public void setOnInfoListener(MediaRecorder.OnInfoListener infoListener) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setInfoListener cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setInfoListener called on an already released recorder!");
+            default:
+                break;
+        }
+        mInfoListener = infoListener;
+    }
+
+    public void setOnErrorListener(MediaRecorder.OnErrorListener errorListener) {
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("setErrorListener cannot be called while recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "setErrorListener called on an already released recorder!");
+            default:
+                break;
+        }
+        mErrorListener = errorListener;
+    }
+
+    private void initializeFilterFramework() {
+        mGraphEnv = newInstance(sCtGraphEnvironment);
+        invoke(mGraphEnv, sGraphEnvironmentCreateGLEnvironment);
+
+        int videoFrameWidth = mProfile.videoFrameWidth;
+        int videoFrameHeight = mProfile.videoFrameHeight;
+        if (mCameraDisplayOrientation == 90 || mCameraDisplayOrientation == 270) {
+            int tmp = videoFrameWidth;
+            videoFrameWidth = videoFrameHeight;
+            videoFrameHeight = tmp;
+        }
+
+        invoke(mGraphEnv, sGraphEnvironmentAddReferences,
+                new Object[] {new Object[] {
+                "textureSourceCallback", mSourceReadyCallback,
+                "recordingWidth", videoFrameWidth,
+                "recordingHeight", videoFrameHeight,
+                "recordingProfile", mProfile,
+                "learningDoneListener", mLearningDoneListener,
+                "recordingDoneListener", mRecordingDoneListener}});
+        mRunner = null;
+        mGraphId = -1;
+        mCurrentEffect = EFFECT_NONE;
+    }
+
+    private synchronized void initializeEffect(boolean forceReset) {
+        if (forceReset ||
+            mCurrentEffect != mEffect ||
+            mCurrentEffect == EFFECT_BACKDROPPER) {
+
+            invoke(mGraphEnv, sGraphEnvironmentAddReferences,
+                    new Object[] {new Object[] {
+                    "previewSurfaceTexture", mPreviewSurfaceTexture,
+                    "previewWidth", mPreviewWidth,
+                    "previewHeight", mPreviewHeight,
+                    "orientation", mOrientationHint}});
+            if (mState == STATE_PREVIEW ||
+                    mState == STATE_STARTING_PREVIEW) {
+                // Switching effects while running. Inform video camera.
+                sendMessage(mCurrentEffect, EFFECT_MSG_SWITCHING_EFFECT);
+            }
+
+            switch (mEffect) {
+                case EFFECT_GOOFY_FACE:
+                    mGraphId = (Integer) invoke(mGraphEnv,
+                            sGraphEnvironmentLoadGraph,
+                            new Object[] {mContext, R.raw.goofy_face});
+                    break;
+                case EFFECT_BACKDROPPER:
+                    sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
+                    mGraphId = (Integer) invoke(mGraphEnv,
+                            sGraphEnvironmentLoadGraph,
+                            new Object[] {mContext, R.raw.backdropper});
+                    break;
+                default:
+                    throw new RuntimeException("Unknown effect ID" + mEffect + "!");
+            }
+            mCurrentEffect = mEffect;
+
+            mOldRunner = mRunner;
+            mRunner = invoke(mGraphEnv, sGraphEnvironmentGetRunner,
+                    new Object[] {mGraphId,
+                    getConstant(sClsGraphEnvironment, "MODE_ASYNCHRONOUS")});
+            invoke(mRunner, sGraphRunnerSetDoneCallback, new Object[] {mRunnerDoneCallback});
+            if (mLogVerbose) {
+                Log.v(TAG, "New runner: " + mRunner
+                      + ". Old runner: " + mOldRunner);
+            }
+            if (mState == STATE_PREVIEW ||
+                    mState == STATE_STARTING_PREVIEW) {
+                // Switching effects while running. Stop existing runner.
+                // The stop callback will take care of starting new runner.
+                mCameraDevice.stopPreview();
+                mCameraDevice.setPreviewTextureAsync(null);
+                invoke(mOldRunner, sGraphRunnerStop);
+            }
+        }
+
+        switch (mCurrentEffect) {
+            case EFFECT_GOOFY_FACE:
+                tryEnableVideoStabilization(true);
+                Object goofyFilter = getGraphFilter(mRunner, "goofyrenderer");
+                setInputValue(goofyFilter, "currentEffect",
+                        ((Integer) mEffectParameter).intValue());
+                break;
+            case EFFECT_BACKDROPPER:
+                tryEnableVideoStabilization(false);
+                Object backgroundSrc = getGraphFilter(mRunner, "background");
+                if (ApiHelper.HAS_EFFECTS_RECORDING_CONTEXT_INPUT) {
+                    // Set the context first before setting sourceUrl to
+                    // guarantee the content URI get resolved properly.
+                    setInputValue(backgroundSrc, "context", mContext);
+                }
+                setInputValue(backgroundSrc, "sourceUrl", mEffectParameter);
+                // For front camera, the background video needs to be mirrored in the
+                // backdropper filter
+                if (mCameraFacing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+                    Object replacer = getGraphFilter(mRunner, "replacer");
+                    setInputValue(replacer, "mirrorBg", true);
+                    if (mLogVerbose) Log.v(TAG, "Setting the background to be mirrored");
+                }
+                break;
+            default:
+                break;
+        }
+        setFaceDetectOrientation();
+        setRecordingOrientation();
+    }
+
+    public synchronized void startPreview() {
+        if (mLogVerbose) Log.v(TAG, "Starting preview (" + this + ")");
+
+        switch (mState) {
+            case STATE_STARTING_PREVIEW:
+            case STATE_PREVIEW:
+                // Already running preview
+                Log.w(TAG, "startPreview called when already running preview");
+                return;
+            case STATE_RECORD:
+                throw new RuntimeException("Cannot start preview when already recording!");
+            case STATE_RELEASED:
+                throw new RuntimeException("setEffect called on an already released recorder!");
+            default:
+                break;
+        }
+
+        if (mEffect == EFFECT_NONE) {
+            throw new RuntimeException("No effect selected!");
+        }
+        if (mEffectParameter == null) {
+            throw new RuntimeException("No effect parameter provided!");
+        }
+        if (mProfile == null) {
+            throw new RuntimeException("No recording profile provided!");
+        }
+        if (mPreviewSurfaceTexture == null) {
+            if (mLogVerbose) Log.v(TAG, "Passed a null surface; waiting for valid one");
+            mState = STATE_WAITING_FOR_SURFACE;
+            return;
+        }
+        if (mCameraDevice == null) {
+            throw new RuntimeException("No camera to record from!");
+        }
+
+        if (mLogVerbose) Log.v(TAG, "Initializing filter framework and running the graph.");
+        initializeFilterFramework();
+
+        initializeEffect(true);
+
+        mState = STATE_STARTING_PREVIEW;
+        invoke(mRunner, sGraphRunnerRun);
+        // Rest of preview startup handled in mSourceReadyCallback
+    }
+
+    private Object invokeObjectEquals(Object proxy, Object[] args) {
+        return Boolean.valueOf(proxy == args[0]);
+    }
+
+    private Object invokeObjectToString() {
+        return "Proxy-" + toString();
+    }
+
+    private void invokeOnLearningDone() {
+        if (mLogVerbose) Log.v(TAG, "Learning done callback triggered");
+        // Called in a processing thread, so have to post message back to UI
+        // thread
+        sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_DONE_LEARNING);
+        enable3ALocks(true);
+    }
+
+    private void invokeOnRunnerDone(Object[] args) {
+        int runnerDoneResult = (Integer) args[0];
+        synchronized (EffectsRecorder.this) {
+            if (mLogVerbose) {
+                Log.v(TAG,
+                      "Graph runner done (" + EffectsRecorder.this
+                      + ", mRunner " + mRunner
+                      + ", mOldRunner " + mOldRunner + ")");
+            }
+            if (runnerDoneResult ==
+                    (Integer) getConstant(sClsGraphRunner, "RESULT_ERROR")) {
+                // Handle error case
+                Log.e(TAG, "Error running filter graph!");
+                Exception e = null;
+                if (mRunner != null) {
+                    e = (Exception) invoke(mRunner, sGraphRunnerGetError);
+                } else if (mOldRunner != null) {
+                    e = (Exception) invoke(mOldRunner, sGraphRunnerGetError);
+                }
+                raiseError(e);
+            }
+            if (mOldRunner != null) {
+                // Tear down old graph if available
+                if (mLogVerbose) Log.v(TAG, "Tearing down old graph.");
+                Object glEnv = getContextGLEnvironment(mGraphEnv);
+                if (glEnv != null && !(Boolean) invoke(glEnv, sGLEnvironmentIsActive)) {
+                    invoke(glEnv, sGLEnvironmentActivate);
+                }
+                getGraphTearDown(mOldRunner,
+                        invoke(mGraphEnv, sGraphEnvironmentGetContext));
+                if (glEnv != null && (Boolean) invoke(glEnv, sGLEnvironmentIsActive)) {
+                    invoke(glEnv, sGLEnvironmentDeactivate);
+                }
+                mOldRunner = null;
+            }
+            if (mState == STATE_PREVIEW ||
+                    mState == STATE_STARTING_PREVIEW) {
+                // Switching effects, start up the new runner
+                if (mLogVerbose) {
+                    Log.v(TAG, "Previous effect halted. Running graph again. state: "
+                            + mState);
+                }
+                tryEnable3ALocks(false);
+                // In case of an error, the graph restarts from beginning and in case
+                // of the BACKDROPPER effect, the learner re-learns the background.
+                // Hence, we need to show the learning dialogue to the user
+                // to avoid recording before the learning is done. Else, the user
+                // could start recording before the learning is done and the new
+                // background comes up later leading to an end result video
+                // with a heterogeneous background.
+                // For BACKDROPPER effect, this path is also executed sometimes at
+                // the end of a normal recording session. In such a case, the graph
+                // does not restart and hence the learner does not re-learn. So we
+                // do not want to show the learning dialogue then.
+                if (runnerDoneResult == (Integer) getConstant(
+                        sClsGraphRunner, "RESULT_ERROR")
+                        && mCurrentEffect == EFFECT_BACKDROPPER) {
+                    sendMessage(EFFECT_BACKDROPPER, EFFECT_MSG_STARTED_LEARNING);
+                }
+                invoke(mRunner, sGraphRunnerRun);
+            } else if (mState != STATE_RELEASED) {
+                // Shutting down effects
+                if (mLogVerbose) Log.v(TAG, "Runner halted, restoring direct preview");
+                tryEnable3ALocks(false);
+                sendMessage(EFFECT_NONE, EFFECT_MSG_EFFECTS_STOPPED);
+            } else {
+                // STATE_RELEASED - camera will be/has been released as well, do nothing.
+            }
+        }
+    }
+
+    private void invokeOnSurfaceTextureSourceReady(Object[] args) {
+        SurfaceTexture source = (SurfaceTexture) args[0];
+        if (mLogVerbose) Log.v(TAG, "SurfaceTexture ready callback received");
+        synchronized (EffectsRecorder.this) {
+            mTextureSource = source;
+
+            if (mState == STATE_CONFIGURE) {
+                // Stop preview happened while the runner was doing startup tasks
+                // Since we haven't started anything up, don't do anything
+                // Rest of cleanup will happen in onRunnerDone
+                if (mLogVerbose) Log.v(TAG, "Ready callback: Already stopped, skipping.");
+                return;
+            }
+            if (mState == STATE_RELEASED) {
+                // EffectsRecorder has been released, so don't touch the camera device
+                // or anything else
+                if (mLogVerbose) Log.v(TAG, "Ready callback: Already released, skipping.");
+                return;
+            }
+            if (source == null) {
+                if (mLogVerbose) {
+                    Log.v(TAG, "Ready callback: source null! Looks like graph was closed!");
+                }
+                if (mState == STATE_PREVIEW ||
+                        mState == STATE_STARTING_PREVIEW ||
+                        mState == STATE_RECORD) {
+                    // A null source here means the graph is shutting down
+                    // unexpectedly, so we need to turn off preview before
+                    // the surface texture goes away.
+                    if (mLogVerbose) {
+                        Log.v(TAG, "Ready callback: State: " + mState
+                                + ". stopCameraPreview");
+                    }
+
+                    stopCameraPreview();
+                }
+                return;
+            }
+
+            // Lock AE/AWB to reduce transition flicker
+            tryEnable3ALocks(true);
+
+            mCameraDevice.stopPreview();
+            if (mLogVerbose) Log.v(TAG, "Runner active, connecting effects preview");
+            mCameraDevice.setPreviewTextureAsync(mTextureSource);
+
+            mCameraDevice.startPreviewAsync();
+
+            // Unlock AE/AWB after preview started
+            tryEnable3ALocks(false);
+
+            mState = STATE_PREVIEW;
+
+            if (mLogVerbose) Log.v(TAG, "Start preview/effect switch complete");
+
+            // Sending a message to listener that preview is complete
+            sendMessage(mCurrentEffect, EFFECT_MSG_PREVIEW_RUNNING);
+        }
+    }
+
+    private void invokeOnRecordingDone() {
+        // Forward the callback to the VideoModule object (as an asynchronous event).
+        if (mLogVerbose) Log.v(TAG, "Recording done callback triggered");
+        sendMessage(EFFECT_NONE, EFFECT_MSG_RECORDING_DONE);
+    }
+
+    public synchronized void startRecording() {
+        if (mLogVerbose) Log.v(TAG, "Starting recording (" + this + ")");
+
+        switch (mState) {
+            case STATE_RECORD:
+                throw new RuntimeException("Already recording, cannot begin anew!");
+            case STATE_RELEASED:
+                throw new RuntimeException(
+                    "startRecording called on an already released recorder!");
+            default:
+                break;
+        }
+
+        if ((mOutputFile == null) && (mFd == null)) {
+            throw new RuntimeException("No output file name or descriptor provided!");
+        }
+
+        if (mState == STATE_CONFIGURE) {
+            startPreview();
+        }
+
+        Object recorder = getGraphFilter(mRunner, "recorder");
+        if (mFd != null) {
+            setInputValue(recorder, "outputFileDescriptor", mFd);
+        } else {
+            setInputValue(recorder, "outputFile", mOutputFile);
+        }
+        // It is ok to set the audiosource without checking for timelapse here
+        // since that check will be done in the MediaEncoderFilter itself
+        setInputValue(recorder, "audioSource", MediaRecorder.AudioSource.CAMCORDER);
+        setInputValue(recorder, "recordingProfile", mProfile);
+        setInputValue(recorder, "orientationHint", mOrientationHint);
+        // Important to set the timelapseinterval to 0 if the capture rate is not >0
+        // since the recorder does not get created every time the recording starts.
+        // The recorder infers whether the capture is timelapsed based on the value of
+        // this interval
+        boolean captureTimeLapse = mCaptureRate > 0;
+        if (captureTimeLapse) {
+            double timeBetweenFrameCapture = 1 / mCaptureRate;
+            setInputValue(recorder, "timelapseRecordingIntervalUs",
+                    (long) (1000000 * timeBetweenFrameCapture));
+
+        } else {
+            setInputValue(recorder, "timelapseRecordingIntervalUs", 0L);
+        }
+
+        if (mInfoListener != null) {
+            setInputValue(recorder, "infoListener", mInfoListener);
+        }
+        if (mErrorListener != null) {
+            setInputValue(recorder, "errorListener", mErrorListener);
+        }
+        setInputValue(recorder, "maxFileSize", mMaxFileSize);
+        setInputValue(recorder, "maxDurationMs", mMaxDurationMs);
+        setInputValue(recorder, "recording", true);
+        mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
+        mState = STATE_RECORD;
+    }
+
+    public synchronized void stopRecording() {
+        if (mLogVerbose) Log.v(TAG, "Stop recording (" + this + ")");
+
+        switch (mState) {
+            case STATE_CONFIGURE:
+            case STATE_STARTING_PREVIEW:
+            case STATE_PREVIEW:
+                Log.w(TAG, "StopRecording called when recording not active!");
+                return;
+            case STATE_RELEASED:
+                throw new RuntimeException("stopRecording called on released EffectsRecorder!");
+            default:
+                break;
+        }
+        Object recorder = getGraphFilter(mRunner, "recorder");
+        setInputValue(recorder, "recording", false);
+        mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
+        mState = STATE_PREVIEW;
+    }
+
+    // Called to tell the filter graph that the display surfacetexture is not valid anymore.
+    // So the filter graph should not hold any reference to the surface created with that.
+    public synchronized void disconnectDisplay() {
+        if (mLogVerbose) Log.v(TAG, "Disconnecting the graph from the " +
+            "SurfaceTexture");
+        Object display = getGraphFilter(mRunner, "display");
+        invoke(display, sSurfaceTextureTargetDisconnect, new Object[] {
+                invoke(mGraphEnv, sGraphEnvironmentGetContext)});
+    }
+
+    // The VideoModule will call this to notify that the camera is being
+    // released to the outside world. This call should happen after the
+    // stopRecording call. Else, the effects may throw an exception.
+    // With the recording stopped, the stopPreview call will not try to
+    // release the camera again.
+    // This must be called in onPause() if the effects are ON.
+    public synchronized void disconnectCamera() {
+        if (mLogVerbose) Log.v(TAG, "Disconnecting the effects from Camera");
+        stopCameraPreview();
+        mCameraDevice = null;
+    }
+
+    // In a normal case, when the disconnect is not called, we should not
+    // set the camera device to null, since on return callback, we try to
+    // enable 3A locks, which need the cameradevice.
+    public synchronized void stopCameraPreview() {
+        if (mLogVerbose) Log.v(TAG, "Stopping camera preview.");
+        if (mCameraDevice == null) {
+            Log.d(TAG, "Camera already null. Nothing to disconnect");
+            return;
+        }
+        mCameraDevice.stopPreview();
+        mCameraDevice.setPreviewTextureAsync(null);
+    }
+
+    // Stop and release effect resources
+    public synchronized void stopPreview() {
+        if (mLogVerbose) Log.v(TAG, "Stopping preview (" + this + ")");
+        switch (mState) {
+            case STATE_CONFIGURE:
+                Log.w(TAG, "StopPreview called when preview not active!");
+                return;
+            case STATE_RELEASED:
+                throw new RuntimeException("stopPreview called on released EffectsRecorder!");
+            default:
+                break;
+        }
+
+        if (mState == STATE_RECORD) {
+            stopRecording();
+        }
+
+        mCurrentEffect = EFFECT_NONE;
+
+        // This will not do anything if the camera has already been disconnected.
+        stopCameraPreview();
+
+        mState = STATE_CONFIGURE;
+        mOldRunner = mRunner;
+        invoke(mRunner, sGraphRunnerStop);
+        mRunner = null;
+        // Rest of stop and release handled in mRunnerDoneCallback
+    }
+
+    // Try to enable/disable video stabilization if supported; otherwise return false
+    // It is called from a synchronized block.
+    boolean tryEnableVideoStabilization(boolean toggle) {
+        if (mLogVerbose) Log.v(TAG, "tryEnableVideoStabilization.");
+        if (mCameraDevice == null) {
+            Log.d(TAG, "Camera already null. Not enabling video stabilization.");
+            return false;
+        }
+        Camera.Parameters params = mCameraDevice.getParameters();
+
+        String vstabSupported = params.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            if (mLogVerbose) Log.v(TAG, "Setting video stabilization to " + toggle);
+            params.set("video-stabilization", toggle ? "true" : "false");
+            mCameraDevice.setParameters(params);
+            return true;
+        }
+        if (mLogVerbose) Log.v(TAG, "Video stabilization not supported");
+        return false;
+    }
+
+    // Try to enable/disable 3A locks if supported; otherwise return false
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    synchronized boolean tryEnable3ALocks(boolean toggle) {
+        if (mLogVerbose) Log.v(TAG, "tryEnable3ALocks");
+        if (mCameraDevice == null) {
+            Log.d(TAG, "Camera already null. Not tryenabling 3A locks.");
+            return false;
+        }
+        Camera.Parameters params = mCameraDevice.getParameters();
+        if (Util.isAutoExposureLockSupported(params) &&
+            Util.isAutoWhiteBalanceLockSupported(params)) {
+            params.setAutoExposureLock(toggle);
+            params.setAutoWhiteBalanceLock(toggle);
+            mCameraDevice.setParameters(params);
+            return true;
+        }
+        return false;
+    }
+
+    // Try to enable/disable 3A locks if supported; otherwise, throw error
+    // Use this when locks are essential to success
+    synchronized void enable3ALocks(boolean toggle) {
+        if (mLogVerbose) Log.v(TAG, "Enable3ALocks");
+        if (mCameraDevice == null) {
+            Log.d(TAG, "Camera already null. Not enabling 3A locks.");
+            return;
+        }
+        Camera.Parameters params = mCameraDevice.getParameters();
+        if (!tryEnable3ALocks(toggle)) {
+            throw new RuntimeException("Attempt to lock 3A on camera with no locking support!");
+        }
+    }
+
+    static class SerializableInvocationHandler
+            implements InvocationHandler, Serializable {
+        private final int mEffectsRecorderIndex;
+        public SerializableInvocationHandler(int index) {
+            mEffectsRecorderIndex = index;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args)
+                throws Throwable {
+            if (sEffectsRecorder == null) return null;
+            if (mEffectsRecorderIndex != sEffectsRecorderIndex) {
+                Log.v(TAG, "Ignore old callback " + mEffectsRecorderIndex);
+                return null;
+            }
+            if (method.equals(sObjectEquals)) {
+                return sEffectsRecorder.invokeObjectEquals(proxy, args);
+            } else if (method.equals(sObjectToString)) {
+                return sEffectsRecorder.invokeObjectToString();
+            } else if (method.equals(sLearningDoneListenerOnLearningDone)) {
+                sEffectsRecorder.invokeOnLearningDone();
+            } else if (method.equals(sOnRunnerDoneListenerOnRunnerDone)) {
+                sEffectsRecorder.invokeOnRunnerDone(args);
+            } else if (method.equals(
+                    sSurfaceTextureSourceListenerOnSurfaceTextureSourceReady)) {
+                sEffectsRecorder.invokeOnSurfaceTextureSourceReady(args);
+            } else if (method.equals(sOnRecordingDoneListenerOnRecordingDone)) {
+                sEffectsRecorder.invokeOnRecordingDone();
+            }
+            return null;
+        }
+    }
+
+    // Indicates that all camera/recording activity needs to halt
+    public synchronized void release() {
+        if (mLogVerbose) Log.v(TAG, "Releasing (" + this + ")");
+
+        switch (mState) {
+            case STATE_RECORD:
+            case STATE_STARTING_PREVIEW:
+            case STATE_PREVIEW:
+                stopPreview();
+                // Fall-through
+            default:
+                if (mSoundPlayer != null) {
+                    mSoundPlayer.release();
+                    mSoundPlayer = null;
+                }
+                mState = STATE_RELEASED;
+                break;
+        }
+        sEffectsRecorder = null;
+    }
+
+    private void sendMessage(final int effect, final int msg) {
+        if (mEffectsListener != null) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mEffectsListener.onEffectsUpdate(effect, msg);
+                }
+            });
+        }
+    }
+
+    private void raiseError(final Exception exception) {
+        if (mEffectsListener != null) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mFd != null) {
+                        mEffectsListener.onEffectsError(exception, null);
+                    } else {
+                        mEffectsListener.onEffectsError(exception, mOutputFile);
+                    }
+                }
+            });
+        }
+    }
+
+    // invoke method on receiver with no arguments
+    private Object invoke(Object receiver, Method method) {
+        try {
+            return method.invoke(receiver);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    // invoke method on receiver with arguments
+    private Object invoke(Object receiver, Method method, Object[] args) {
+        try {
+            return method.invoke(receiver, args);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void setInputValue(Object receiver, String key, Object value) {
+        try {
+            sFilterSetInputValue.invoke(receiver, new Object[] {key, value});
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Object newInstance(Constructor<?> ct, Object[] initArgs) {
+        try {
+            return ct.newInstance(initArgs);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Object newInstance(Constructor<?> ct) {
+        try {
+            return ct.newInstance();
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Object getGraphFilter(Object receiver, String name) {
+        try {
+            return sFilterGraphGetFilter.invoke(sGraphRunnerGetGraph
+                    .invoke(receiver), new Object[] {name});
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Object getContextGLEnvironment(Object receiver) {
+        try {
+            return sFilterContextGetGLEnvironment
+                    .invoke(sGraphEnvironmentGetContext.invoke(receiver));
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private void getGraphTearDown(Object receiver, Object filterContext) {
+        try {
+            sFilterGraphTearDown.invoke(sGraphRunnerGetGraph.invoke(receiver),
+                    new Object[]{filterContext});
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Object getConstant(Class<?> cls, String name) {
+        try {
+            return cls.getDeclaredField(name).get(null);
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+}
diff --git a/src/com/android/camera/Exif.java b/src/com/android/camera/Exif.java
new file mode 100644
index 0000000..c6ec6af
--- /dev/null
+++ b/src/com/android/camera/Exif.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 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.util.Log;
+
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.IOException;
+
+public class Exif {
+    private static final String TAG = "CameraExif";
+
+    public static ExifInterface getExif(byte[] jpegData) {
+        ExifInterface exif = new ExifInterface();
+        try {
+            exif.readExif(jpegData);
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to read EXIF data", e);
+        }
+        return exif;
+    }
+
+    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
+    public static int getOrientation(ExifInterface exif) {
+        Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+        if (val == null) {
+            return 0;
+        } else {
+            return ExifInterface.getRotationForOrientationValue(val.shortValue());
+        }
+    }
+
+    public static int getOrientation(byte[] jpegData) {
+        if (jpegData == null) return 0;
+
+        ExifInterface exif = getExif(jpegData);
+        return getOrientation(exif);
+    }
+}
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java
new file mode 100644
index 0000000..8bcb52f
--- /dev/null
+++ b/src/com/android/camera/FocusOverlayManager.java
@@ -0,0 +1,558 @@
+/*
+ * Copyright (C) 2012 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.annotation.TargetApi;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.Camera.Area;
+import android.hardware.Camera.Parameters;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/* A class that handles everything about focus in still picture mode.
+ * This also handles the metering area because it is the same as focus area.
+ *
+ * The test cases:
+ * (1) The camera has continuous autofocus. Move the camera. Take a picture when
+ *     CAF is not in progress.
+ * (2) The camera has continuous autofocus. Move the camera. Take a picture when
+ *     CAF is in progress.
+ * (3) The camera has face detection. Point the camera at some faces. Hold the
+ *     shutter. Release to take a picture.
+ * (4) The camera has face detection. Point the camera at some faces. Single tap
+ *     the shutter to take a picture.
+ * (5) The camera has autofocus. Single tap the shutter to take a picture.
+ * (6) The camera has autofocus. Hold the shutter. Release to take a picture.
+ * (7) The camera has no autofocus. Single tap the shutter and take a picture.
+ * (8) The camera has autofocus and supports focus area. Touch the screen to
+ *     trigger autofocus. Take a picture.
+ * (9) The camera has autofocus and supports focus area. Touch the screen to
+ *     trigger autofocus. Wait until it times out.
+ * (10) The camera has no autofocus and supports metering area. Touch the screen
+ *     to change metering area.
+ */
+public class FocusOverlayManager {
+    private static final String TAG = "CAM_FocusManager";
+
+    private static final int RESET_TOUCH_FOCUS = 0;
+    private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
+
+    private int mState = STATE_IDLE;
+    private static final int STATE_IDLE = 0; // Focus is not active.
+    private static final int STATE_FOCUSING = 1; // Focus is in progress.
+    // Focus is in progress and the camera should take a picture after focus finishes.
+    private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
+    private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
+    private static final int STATE_FAIL = 4; // Focus finishes and fails.
+
+    private boolean mInitialized;
+    private boolean mFocusAreaSupported;
+    private boolean mMeteringAreaSupported;
+    private boolean mLockAeAwbNeeded;
+    private boolean mAeAwbLock;
+    private Matrix mMatrix;
+
+    private int mPreviewWidth; // The width of the preview frame layout.
+    private int mPreviewHeight; // The height of the preview frame layout.
+    private boolean mMirror; // true if the camera is front-facing.
+    private int mDisplayOrientation;
+    private List<Object> mFocusArea; // focus area in driver format
+    private List<Object> mMeteringArea; // metering area in driver format
+    private String mFocusMode;
+    private String[] mDefaultFocusModes;
+    private String mOverrideFocusMode;
+    private Parameters mParameters;
+    private ComboPreferences mPreferences;
+    private Handler mHandler;
+    Listener mListener;
+    private boolean mPreviousMoving;
+    private boolean mFocusDefault;
+
+    private FocusUI mUI;
+
+    public  interface FocusUI {
+        public boolean hasFaces();
+        public void clearFocus();
+        public void setFocusPosition(int x, int y);
+        public void onFocusStarted();
+        public void onFocusSucceeded(boolean timeOut);
+        public void onFocusFailed(boolean timeOut);
+        public void pauseFaceDetection();
+        public void resumeFaceDetection();
+    }
+
+    public interface Listener {
+        public void autoFocus();
+        public void cancelAutoFocus();
+        public boolean capture();
+        public void startFaceDetection();
+        public void stopFaceDetection();
+        public void setFocusParameters();
+    }
+
+    private class MainHandler extends Handler {
+        public MainHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case RESET_TOUCH_FOCUS: {
+                    cancelAutoFocus();
+                    mListener.startFaceDetection();
+                    break;
+                }
+            }
+        }
+    }
+
+    public FocusOverlayManager(ComboPreferences preferences, String[] defaultFocusModes,
+            Parameters parameters, Listener listener,
+            boolean mirror, Looper looper, FocusUI ui) {
+        mHandler = new MainHandler(looper);
+        mMatrix = new Matrix();
+        mPreferences = preferences;
+        mDefaultFocusModes = defaultFocusModes;
+        setParameters(parameters);
+        mListener = listener;
+        setMirror(mirror);
+        mFocusDefault = true;
+        mUI = ui;
+    }
+
+    public void setParameters(Parameters parameters) {
+        // parameters can only be null when onConfigurationChanged is called
+        // before camera is open. We will just return in this case, because
+        // parameters will be set again later with the right parameters after
+        // camera is open.
+        if (parameters == null) return;
+        mParameters = parameters;
+        mFocusAreaSupported = Util.isFocusAreaSupported(parameters);
+        mMeteringAreaSupported = Util.isMeteringAreaSupported(parameters);
+        mLockAeAwbNeeded = (Util.isAutoExposureLockSupported(mParameters) ||
+                Util.isAutoWhiteBalanceLockSupported(mParameters));
+    }
+
+    public void setPreviewSize(int previewWidth, int previewHeight) {
+        if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
+            mPreviewWidth = previewWidth;
+            mPreviewHeight = previewHeight;
+            setMatrix();
+        }
+    }
+
+    public void setMirror(boolean mirror) {
+        mMirror = mirror;
+        setMatrix();
+    }
+
+    public void setDisplayOrientation(int displayOrientation) {
+        mDisplayOrientation = displayOrientation;
+        setMatrix();
+    }
+
+    private void setMatrix() {
+        if (mPreviewWidth != 0 && mPreviewHeight != 0) {
+            Matrix matrix = new Matrix();
+            Util.prepareMatrix(matrix, mMirror, mDisplayOrientation,
+                    mPreviewWidth, mPreviewHeight);
+            // In face detection, the matrix converts the driver coordinates to UI
+            // coordinates. In tap focus, the inverted matrix converts the UI
+            // coordinates to driver coordinates.
+            matrix.invert(mMatrix);
+            mInitialized = true;
+        }
+    }
+
+    private void lockAeAwbIfNeeded() {
+        if (mLockAeAwbNeeded && !mAeAwbLock) {
+            mAeAwbLock = true;
+            mListener.setFocusParameters();
+        }
+    }
+
+    private void unlockAeAwbIfNeeded() {
+        if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
+            mAeAwbLock = false;
+            mListener.setFocusParameters();
+        }
+    }
+
+    public void onShutterDown() {
+        if (!mInitialized) return;
+
+        boolean autoFocusCalled = false;
+        if (needAutoFocusCall()) {
+            // Do not focus if touch focus has been triggered.
+            if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
+                autoFocus();
+                autoFocusCalled = true;
+            }
+        }
+
+        if (!autoFocusCalled) lockAeAwbIfNeeded();
+    }
+
+    public void onShutterUp() {
+        if (!mInitialized) return;
+
+        if (needAutoFocusCall()) {
+            // User releases half-pressed focus key.
+            if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
+                    || mState == STATE_FAIL) {
+                cancelAutoFocus();
+            }
+        }
+
+        // Unlock AE and AWB after cancelAutoFocus. Camera API does not
+        // guarantee setParameters can be called during autofocus.
+        unlockAeAwbIfNeeded();
+    }
+
+    public void doSnap() {
+        if (!mInitialized) return;
+
+        // If the user has half-pressed the shutter and focus is completed, we
+        // can take the photo right away. If the focus mode is infinity, we can
+        // also take the photo.
+        if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
+            capture();
+        } else if (mState == STATE_FOCUSING) {
+            // Half pressing the shutter (i.e. the focus button event) will
+            // already have requested AF for us, so just request capture on
+            // focus here.
+            mState = STATE_FOCUSING_SNAP_ON_FINISH;
+        } else if (mState == STATE_IDLE) {
+            // We didn't do focus. This can happen if the user press focus key
+            // while the snapshot is still in progress. The user probably wants
+            // the next snapshot as soon as possible, so we just do a snapshot
+            // without focusing again.
+            capture();
+        }
+    }
+
+    public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
+        if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
+            // Take the picture no matter focus succeeds or fails. No need
+            // to play the AF sound if we're about to play the shutter
+            // sound.
+            if (focused) {
+                mState = STATE_SUCCESS;
+            } else {
+                mState = STATE_FAIL;
+            }
+            updateFocusUI();
+            capture();
+        } else if (mState == STATE_FOCUSING) {
+            // This happens when (1) user is half-pressing the focus key or
+            // (2) touch focus is triggered. Play the focus tone. Do not
+            // take the picture now.
+            if (focused) {
+                mState = STATE_SUCCESS;
+            } else {
+                mState = STATE_FAIL;
+            }
+            updateFocusUI();
+            // If this is triggered by touch focus, cancel focus after a
+            // while.
+            if (!mFocusDefault) {
+                mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
+            }
+            if (shutterButtonPressed) {
+                // Lock AE & AWB so users can half-press shutter and recompose.
+                lockAeAwbIfNeeded();
+            }
+        } else if (mState == STATE_IDLE) {
+            // User has released the focus key before focus completes.
+            // Do nothing.
+        }
+    }
+
+    public void onAutoFocusMoving(boolean moving) {
+        if (!mInitialized) return;
+
+
+        // Ignore if the camera has detected some faces.
+        if (mUI.hasFaces()) {
+            mUI.clearFocus();
+            return;
+        }
+
+        // Ignore if we have requested autofocus. This method only handles
+        // continuous autofocus.
+        if (mState != STATE_IDLE) return;
+
+        // animate on false->true trasition only b/8219520
+        if (moving && !mPreviousMoving) {
+            mUI.onFocusStarted();
+        } else if (!moving) {
+            mUI.onFocusSucceeded(true);
+        }
+        mPreviousMoving = moving;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void initializeFocusAreas(int x, int y) {
+        if (mFocusArea == null) {
+            mFocusArea = new ArrayList<Object>();
+            mFocusArea.add(new Area(new Rect(), 1));
+        }
+
+        // Convert the coordinates to driver format.
+        calculateTapArea(x, y, 1f, ((Area) mFocusArea.get(0)).rect);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void initializeMeteringAreas(int x, int y) {
+        if (mMeteringArea == null) {
+            mMeteringArea = new ArrayList<Object>();
+            mMeteringArea.add(new Area(new Rect(), 1));
+        }
+
+        // Convert the coordinates to driver format.
+        // AE area is bigger because exposure is sensitive and
+        // easy to over- or underexposure if area is too small.
+        calculateTapArea(x, y, 1.5f, ((Area) mMeteringArea.get(0)).rect);
+    }
+
+    public void onSingleTapUp(int x, int y) {
+        if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return;
+
+        // Let users be able to cancel previous touch focus.
+        if ((!mFocusDefault) && (mState == STATE_FOCUSING ||
+                    mState == STATE_SUCCESS || mState == STATE_FAIL)) {
+            cancelAutoFocus();
+        }
+        if (mPreviewWidth == 0 || mPreviewHeight == 0) return;
+        mFocusDefault = false;
+        // Initialize mFocusArea.
+        if (mFocusAreaSupported) {
+            initializeFocusAreas(x, y);
+        }
+        // Initialize mMeteringArea.
+        if (mMeteringAreaSupported) {
+            initializeMeteringAreas(x, y);
+        }
+
+        // Use margin to set the focus indicator to the touched area.
+        mUI.setFocusPosition(x, y);
+
+        // Stop face detection because we want to specify focus and metering area.
+        mListener.stopFaceDetection();
+
+        // Set the focus area and metering area.
+        mListener.setFocusParameters();
+        if (mFocusAreaSupported) {
+            autoFocus();
+        } else {  // Just show the indicator in all other cases.
+            updateFocusUI();
+            // Reset the metering area in 3 seconds.
+            mHandler.removeMessages(RESET_TOUCH_FOCUS);
+            mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
+        }
+    }
+
+    public void onPreviewStarted() {
+        mState = STATE_IDLE;
+    }
+
+    public void onPreviewStopped() {
+        // If auto focus was in progress, it would have been stopped.
+        mState = STATE_IDLE;
+        resetTouchFocus();
+        updateFocusUI();
+    }
+
+    public void onCameraReleased() {
+        onPreviewStopped();
+    }
+
+    private void autoFocus() {
+        Log.v(TAG, "Start autofocus.");
+        mListener.autoFocus();
+        mState = STATE_FOCUSING;
+        // Pause the face view because the driver will keep sending face
+        // callbacks after the focus completes.
+        mUI.pauseFaceDetection();
+        updateFocusUI();
+        mHandler.removeMessages(RESET_TOUCH_FOCUS);
+    }
+
+    private void cancelAutoFocus() {
+        Log.v(TAG, "Cancel autofocus.");
+
+        // Reset the tap area before calling mListener.cancelAutofocus.
+        // Otherwise, focus mode stays at auto and the tap area passed to the
+        // driver is not reset.
+        resetTouchFocus();
+        mListener.cancelAutoFocus();
+        mUI.resumeFaceDetection();
+        mState = STATE_IDLE;
+        updateFocusUI();
+        mHandler.removeMessages(RESET_TOUCH_FOCUS);
+    }
+
+    private void capture() {
+        if (mListener.capture()) {
+            mState = STATE_IDLE;
+            mHandler.removeMessages(RESET_TOUCH_FOCUS);
+        }
+    }
+
+    public String getFocusMode() {
+        if (mOverrideFocusMode != null) return mOverrideFocusMode;
+        if (mParameters == null) return Parameters.FOCUS_MODE_AUTO;
+        List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
+
+        if (mFocusAreaSupported && !mFocusDefault) {
+            // Always use autofocus in tap-to-focus.
+            mFocusMode = Parameters.FOCUS_MODE_AUTO;
+        } else {
+            // The default is continuous autofocus.
+            mFocusMode = mPreferences.getString(
+                    CameraSettings.KEY_FOCUS_MODE, null);
+
+            // Try to find a supported focus mode from the default list.
+            if (mFocusMode == null) {
+                for (int i = 0; i < mDefaultFocusModes.length; i++) {
+                    String mode = mDefaultFocusModes[i];
+                    if (Util.isSupported(mode, supportedFocusModes)) {
+                        mFocusMode = mode;
+                        break;
+                    }
+                }
+            }
+        }
+        if (!Util.isSupported(mFocusMode, supportedFocusModes)) {
+            // For some reasons, the driver does not support the current
+            // focus mode. Fall back to auto.
+            if (Util.isSupported(Parameters.FOCUS_MODE_AUTO,
+                    mParameters.getSupportedFocusModes())) {
+                mFocusMode = Parameters.FOCUS_MODE_AUTO;
+            } else {
+                mFocusMode = mParameters.getFocusMode();
+            }
+        }
+        return mFocusMode;
+    }
+
+    public List getFocusAreas() {
+        return mFocusArea;
+    }
+
+    public List getMeteringAreas() {
+        return mMeteringArea;
+    }
+
+    public void updateFocusUI() {
+        if (!mInitialized) return;
+        // Show only focus indicator or face indicator.
+
+        if (mState == STATE_IDLE) {
+            if (mFocusDefault) {
+                mUI.clearFocus();
+            } else {
+                // Users touch on the preview and the indicator represents the
+                // metering area. Either focus area is not supported or
+                // autoFocus call is not required.
+                mUI.onFocusStarted();
+            }
+        } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
+            mUI.onFocusStarted();
+        } else {
+            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
+                // TODO: check HAL behavior and decide if this can be removed.
+                mUI.onFocusSucceeded(false);
+            } else if (mState == STATE_SUCCESS) {
+                mUI.onFocusSucceeded(false);
+            } else if (mState == STATE_FAIL) {
+                mUI.onFocusFailed(false);
+            }
+        }
+    }
+
+    public void resetTouchFocus() {
+        if (!mInitialized) return;
+
+        // Put focus indicator to the center. clear reset position
+        mUI.clearFocus();
+        // Initialize mFocusArea.
+        if (mFocusAreaSupported) {
+            initializeFocusAreas(mPreviewWidth / 2, mPreviewHeight / 2);
+        }
+        // Initialize mMeteringArea.
+        if (mMeteringAreaSupported) {
+            initializeMeteringAreas(mPreviewWidth / 2, mPreviewHeight / 2);
+        }
+        mFocusDefault = true;
+    }
+
+    private void calculateTapArea(int x, int y, float areaMultiple, Rect rect) {
+        int areaSize = (int) (Math.min(mPreviewWidth, mPreviewHeight) * areaMultiple / 20);
+        int left = Util.clamp(x - areaSize, 0, mPreviewWidth - 2 * areaSize);
+        int top = Util.clamp(y - areaSize, 0, mPreviewHeight - 2 * areaSize);
+
+        RectF rectF = new RectF(left, top, left + 2 * areaSize, top + 2 * areaSize);
+        mMatrix.mapRect(rectF);
+        Util.rectFToRect(rectF, rect);
+    }
+
+    /* package */ int getFocusState() {
+        return mState;
+    }
+
+    public boolean isFocusCompleted() {
+        return mState == STATE_SUCCESS || mState == STATE_FAIL;
+    }
+
+    public boolean isFocusingSnapOnFinish() {
+        return mState == STATE_FOCUSING_SNAP_ON_FINISH;
+    }
+
+    public void removeMessages() {
+        mHandler.removeMessages(RESET_TOUCH_FOCUS);
+    }
+
+    public void overrideFocusMode(String focusMode) {
+        mOverrideFocusMode = focusMode;
+    }
+
+    public void setAeAwbLock(boolean lock) {
+        mAeAwbLock = lock;
+    }
+
+    public boolean getAeAwbLock() {
+        return mAeAwbLock;
+    }
+
+    private boolean needAutoFocusCall() {
+        String focusMode = getFocusMode();
+        return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
+                || focusMode.equals(Parameters.FOCUS_MODE_FIXED)
+                || focusMode.equals(Parameters.FOCUS_MODE_EDOF));
+    }
+}
diff --git a/src/com/android/camera/IconListPreference.java b/src/com/android/camera/IconListPreference.java
new file mode 100644
index 0000000..e5f75d3
--- /dev/null
+++ b/src/com/android/camera/IconListPreference.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.R;
+
+import java.util.List;
+
+/** A {@code ListPreference} where each entry has a corresponding icon. */
+public class IconListPreference extends ListPreference {
+    private int mSingleIconId;
+    private int mIconIds[];
+    private int mLargeIconIds[];
+    private int mImageIds[];
+    private boolean mUseSingleIcon;
+
+    public IconListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.IconListPreference, 0, 0);
+        Resources res = context.getResources();
+        mSingleIconId = a.getResourceId(
+                R.styleable.IconListPreference_singleIcon, 0);
+        mIconIds = getIds(res, a.getResourceId(
+                R.styleable.IconListPreference_icons, 0));
+        mLargeIconIds = getIds(res, a.getResourceId(
+                R.styleable.IconListPreference_largeIcons, 0));
+        mImageIds = getIds(res, a.getResourceId(
+                R.styleable.IconListPreference_images, 0));
+        a.recycle();
+    }
+
+    public int getSingleIcon() {
+        return mSingleIconId;
+    }
+
+    public int[] getIconIds() {
+        return mIconIds;
+    }
+
+    public int[] getLargeIconIds() {
+        return mLargeIconIds;
+    }
+
+    public int[] getImageIds() {
+        return mImageIds;
+    }
+
+    public boolean getUseSingleIcon() {
+        return mUseSingleIcon;
+    }
+
+    public void setIconIds(int[] iconIds) {
+        mIconIds = iconIds;
+    }
+
+    public void setLargeIconIds(int[] largeIconIds) {
+        mLargeIconIds = largeIconIds;
+    }
+
+    public void setUseSingleIcon(boolean useSingle) {
+        mUseSingleIcon = useSingle;
+    }
+
+    private int[] getIds(Resources res, int iconsRes) {
+        if (iconsRes == 0) return null;
+        TypedArray array = res.obtainTypedArray(iconsRes);
+        int n = array.length();
+        int ids[] = new int[n];
+        for (int i = 0; i < n; ++i) {
+            ids[i] = array.getResourceId(i, 0);
+        }
+        array.recycle();
+        return ids;
+    }
+
+    @Override
+    public void filterUnsupported(List<String> supported) {
+        CharSequence entryValues[] = getEntryValues();
+        IntArray iconIds = new IntArray();
+        IntArray largeIconIds = new IntArray();
+        IntArray imageIds = new IntArray();
+
+        for (int i = 0, len = entryValues.length; i < len; i++) {
+            if (supported.indexOf(entryValues[i].toString()) >= 0) {
+                if (mIconIds != null) iconIds.add(mIconIds[i]);
+                if (mLargeIconIds != null) largeIconIds.add(mLargeIconIds[i]);
+                if (mImageIds != null) imageIds.add(mImageIds[i]);
+            }
+        }
+        if (mIconIds != null) mIconIds = iconIds.toArray(new int[iconIds.size()]);
+        if (mLargeIconIds != null) {
+            mLargeIconIds = largeIconIds.toArray(new int[largeIconIds.size()]);
+        }
+        if (mImageIds != null) mImageIds = imageIds.toArray(new int[imageIds.size()]);
+        super.filterUnsupported(supported);
+    }
+}
diff --git a/src/com/android/camera/IntArray.java b/src/com/android/camera/IntArray.java
new file mode 100644
index 0000000..a2550db
--- /dev/null
+++ b/src/com/android/camera/IntArray.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 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;
+
+public class IntArray {
+    private static final int INIT_CAPACITY = 8;
+
+    private int mData[] = new int[INIT_CAPACITY];
+    private int mSize = 0;
+
+    public void add(int value) {
+        if (mData.length == mSize) {
+            int temp[] = new int[mSize + mSize];
+            System.arraycopy(mData, 0, temp, 0, mSize);
+            mData = temp;
+        }
+        mData[mSize++] = value;
+    }
+
+    public int size() {
+        return mSize;
+    }
+
+    public int[] toArray(int[] result) {
+        if (result == null || result.length < mSize) {
+            result = new int[mSize];
+        }
+        System.arraycopy(mData, 0, result, 0, mSize);
+        return result;
+    }
+}
diff --git a/src/com/android/camera/ListPreference.java b/src/com/android/camera/ListPreference.java
new file mode 100644
index 0000000..38866de
--- /dev/null
+++ b/src/com/android/camera/ListPreference.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A type of <code>CameraPreference</code> whose number of possible values
+ * is limited.
+ */
+public class ListPreference extends CameraPreference {
+    private static final String TAG = "ListPreference";
+    private final String mKey;
+    private String mValue;
+    private final CharSequence[] mDefaultValues;
+
+    private CharSequence[] mEntries;
+    private CharSequence[] mEntryValues;
+    private CharSequence[] mLabels;
+    private boolean mLoaded = false;
+
+    public ListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.ListPreference, 0, 0);
+
+        mKey = Util.checkNotNull(
+                a.getString(R.styleable.ListPreference_key));
+
+        // We allow the defaultValue attribute to be a string or an array of
+        // strings. The reason we need multiple default values is that some
+        // of them may be unsupported on a specific platform (for example,
+        // continuous auto-focus). In that case the first supported value
+        // in the array will be used.
+        int attrDefaultValue = R.styleable.ListPreference_defaultValue;
+        TypedValue tv = a.peekValue(attrDefaultValue);
+        if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
+            mDefaultValues = a.getTextArray(attrDefaultValue);
+        } else {
+            mDefaultValues = new CharSequence[1];
+            mDefaultValues[0] = a.getString(attrDefaultValue);
+        }
+
+        setEntries(a.getTextArray(R.styleable.ListPreference_entries));
+        setEntryValues(a.getTextArray(
+                R.styleable.ListPreference_entryValues));
+        setLabels(a.getTextArray(
+                R.styleable.ListPreference_labelList));
+        a.recycle();
+    }
+
+    public String getKey() {
+        return mKey;
+    }
+
+    public CharSequence[] getEntries() {
+        return mEntries;
+    }
+
+    public CharSequence[] getEntryValues() {
+        return mEntryValues;
+    }
+
+    public CharSequence[] getLabels() {
+        return mLabels;
+    }
+
+    public void setEntries(CharSequence entries[]) {
+        mEntries = entries == null ? new CharSequence[0] : entries;
+    }
+
+    public void setEntryValues(CharSequence values[]) {
+        mEntryValues = values == null ? new CharSequence[0] : values;
+    }
+
+    public void setLabels(CharSequence labels[]) {
+        mLabels = labels == null ? new CharSequence[0] : labels;
+    }
+
+    public String getValue() {
+        if (!mLoaded) {
+            mValue = getSharedPreferences().getString(mKey,
+                    findSupportedDefaultValue());
+            mLoaded = true;
+        }
+        return mValue;
+    }
+
+    // Find the first value in mDefaultValues which is supported.
+    private String findSupportedDefaultValue() {
+        for (int i = 0; i < mDefaultValues.length; i++) {
+            for (int j = 0; j < mEntryValues.length; j++) {
+                // Note that mDefaultValues[i] may be null (if unspecified
+                // in the xml file).
+                if (mEntryValues[j].equals(mDefaultValues[i])) {
+                    return mDefaultValues[i].toString();
+                }
+            }
+        }
+        return null;
+    }
+
+    public void setValue(String value) {
+        if (findIndexOfValue(value) < 0) throw new IllegalArgumentException();
+        mValue = value;
+        persistStringValue(value);
+    }
+
+    public void setValueIndex(int index) {
+        setValue(mEntryValues[index].toString());
+    }
+
+    public int findIndexOfValue(String value) {
+        for (int i = 0, n = mEntryValues.length; i < n; ++i) {
+            if (Util.equals(mEntryValues[i], value)) return i;
+        }
+        return -1;
+    }
+
+    public int getCurrentIndex() {
+        return findIndexOfValue(getValue());
+    }
+
+    public String getEntry() {
+        return mEntries[findIndexOfValue(getValue())].toString();
+    }
+
+    public String getLabel() {
+        return mLabels[findIndexOfValue(getValue())].toString();
+    }
+
+    protected void persistStringValue(String value) {
+        SharedPreferences.Editor editor = getSharedPreferences().edit();
+        editor.putString(mKey, value);
+        editor.apply();
+    }
+
+    @Override
+    public void reloadValue() {
+        this.mLoaded = false;
+    }
+
+    public void filterUnsupported(List<String> supported) {
+        ArrayList<CharSequence> entries = new ArrayList<CharSequence>();
+        ArrayList<CharSequence> entryValues = new ArrayList<CharSequence>();
+        for (int i = 0, len = mEntryValues.length; i < len; i++) {
+            if (supported.indexOf(mEntryValues[i].toString()) >= 0) {
+                entries.add(mEntries[i]);
+                entryValues.add(mEntryValues[i]);
+            }
+        }
+        int size = entries.size();
+        mEntries = entries.toArray(new CharSequence[size]);
+        mEntryValues = entryValues.toArray(new CharSequence[size]);
+    }
+
+    public void filterDuplicated() {
+        ArrayList<CharSequence> entries = new ArrayList<CharSequence>();
+        ArrayList<CharSequence> entryValues = new ArrayList<CharSequence>();
+        for (int i = 0, len = mEntryValues.length; i < len; i++) {
+            if (!entries.contains(mEntries[i])) {
+                entries.add(mEntries[i]);
+                entryValues.add(mEntryValues[i]);
+            }
+        }
+        int size = entries.size();
+        mEntries = entries.toArray(new CharSequence[size]);
+        mEntryValues = entryValues.toArray(new CharSequence[size]);
+    }
+
+    public void print() {
+        Log.v(TAG, "Preference key=" + getKey() + ". value=" + getValue());
+        for (int i = 0; i < mEntryValues.length; i++) {
+            Log.v(TAG, "entryValues[" + i + "]=" + mEntryValues[i]);
+        }
+    }
+}
diff --git a/src/com/android/camera/LocationManager.java b/src/com/android/camera/LocationManager.java
new file mode 100644
index 0000000..fcf21b6
--- /dev/null
+++ b/src/com/android/camera/LocationManager.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import android.location.Location;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A class that handles everything about location.
+ */
+public class LocationManager {
+    private static final String TAG = "LocationManager";
+
+    private Context mContext;
+    private Listener mListener;
+    private android.location.LocationManager mLocationManager;
+    private boolean mRecordLocation;
+
+    LocationListener [] mLocationListeners = new LocationListener[] {
+            new LocationListener(android.location.LocationManager.GPS_PROVIDER),
+            new LocationListener(android.location.LocationManager.NETWORK_PROVIDER)
+    };
+
+    public interface Listener {
+        public void showGpsOnScreenIndicator(boolean hasSignal);
+        public void hideGpsOnScreenIndicator();
+   }
+
+    public LocationManager(Context context, Listener listener) {
+        mContext = context;
+        mListener = listener;
+    }
+
+    public Location getCurrentLocation() {
+        if (!mRecordLocation) return null;
+
+        // go in best to worst order
+        for (int i = 0; i < mLocationListeners.length; i++) {
+            Location l = mLocationListeners[i].current();
+            if (l != null) return l;
+        }
+        Log.d(TAG, "No location received yet.");
+        return null;
+    }
+
+    public void recordLocation(boolean recordLocation) {
+        if (mRecordLocation != recordLocation) {
+            mRecordLocation = recordLocation;
+            if (recordLocation) {
+                startReceivingLocationUpdates();
+            } else {
+                stopReceivingLocationUpdates();
+            }
+        }
+    }
+
+    private void startReceivingLocationUpdates() {
+        if (mLocationManager == null) {
+            mLocationManager = (android.location.LocationManager)
+                    mContext.getSystemService(Context.LOCATION_SERVICE);
+        }
+        if (mLocationManager != null) {
+            try {
+                mLocationManager.requestLocationUpdates(
+                        android.location.LocationManager.NETWORK_PROVIDER,
+                        1000,
+                        0F,
+                        mLocationListeners[1]);
+            } catch (SecurityException ex) {
+                Log.i(TAG, "fail to request location update, ignore", ex);
+            } catch (IllegalArgumentException ex) {
+                Log.d(TAG, "provider does not exist " + ex.getMessage());
+            }
+            try {
+                mLocationManager.requestLocationUpdates(
+                        android.location.LocationManager.GPS_PROVIDER,
+                        1000,
+                        0F,
+                        mLocationListeners[0]);
+                if (mListener != null) mListener.showGpsOnScreenIndicator(false);
+            } catch (SecurityException ex) {
+                Log.i(TAG, "fail to request location update, ignore", ex);
+            } catch (IllegalArgumentException ex) {
+                Log.d(TAG, "provider does not exist " + ex.getMessage());
+            }
+            Log.d(TAG, "startReceivingLocationUpdates");
+        }
+    }
+
+    private void stopReceivingLocationUpdates() {
+        if (mLocationManager != null) {
+            for (int i = 0; i < mLocationListeners.length; i++) {
+                try {
+                    mLocationManager.removeUpdates(mLocationListeners[i]);
+                } catch (Exception ex) {
+                    Log.i(TAG, "fail to remove location listners, ignore", ex);
+                }
+            }
+            Log.d(TAG, "stopReceivingLocationUpdates");
+        }
+        if (mListener != null) mListener.hideGpsOnScreenIndicator();
+    }
+
+    private class LocationListener
+            implements android.location.LocationListener {
+        Location mLastLocation;
+        boolean mValid = false;
+        String mProvider;
+
+        public LocationListener(String provider) {
+            mProvider = provider;
+            mLastLocation = new Location(mProvider);
+        }
+
+        @Override
+        public void onLocationChanged(Location newLocation) {
+            if (newLocation.getLatitude() == 0.0
+                    && newLocation.getLongitude() == 0.0) {
+                // Hack to filter out 0.0,0.0 locations
+                return;
+            }
+            // If GPS is available before start camera, we won't get status
+            // update so update GPS indicator when we receive data.
+            if (mListener != null && mRecordLocation &&
+                    android.location.LocationManager.GPS_PROVIDER.equals(mProvider)) {
+                mListener.showGpsOnScreenIndicator(true);
+            }
+            if (!mValid) {
+                Log.d(TAG, "Got first location.");
+            }
+            mLastLocation.set(newLocation);
+            mValid = true;
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+            mValid = false;
+        }
+
+        @Override
+        public void onStatusChanged(
+                String provider, int status, Bundle extras) {
+            switch(status) {
+                case LocationProvider.OUT_OF_SERVICE:
+                case LocationProvider.TEMPORARILY_UNAVAILABLE: {
+                    mValid = false;
+                    if (mListener != null && mRecordLocation &&
+                            android.location.LocationManager.GPS_PROVIDER.equals(provider)) {
+                        mListener.showGpsOnScreenIndicator(false);
+                    }
+                    break;
+                }
+            }
+        }
+
+        public Location current() {
+            return mValid ? mLastLocation : null;
+        }
+    }
+}
diff --git a/src/com/android/camera/MediaSaveService.java b/src/com/android/camera/MediaSaveService.java
new file mode 100644
index 0000000..e37b45c
--- /dev/null
+++ b/src/com/android/camera/MediaSaveService.java
@@ -0,0 +1,225 @@
+/*
+ * 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.app.Service;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.location.Location;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.IBinder;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.File;
+
+/*
+ * Service for saving images in the background thread.
+ */
+public class MediaSaveService extends Service {
+    private static final int SAVE_TASK_LIMIT = 3;
+    private static final String TAG = MediaSaveService.class.getSimpleName();
+
+    private final IBinder mBinder = new LocalBinder();
+    private int mTaskNumber;
+    private Listener mListener;
+
+    interface Listener {
+
+        public void onQueueStatus(boolean full);
+    }
+
+    interface OnMediaSavedListener {
+        public void onMediaSaved(Uri uri);
+    }
+
+    class LocalBinder extends Binder {
+        public MediaSaveService getService() {
+            return MediaSaveService.this;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flag, int startId) {
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+    }
+
+    @Override
+    public void onCreate() {
+        mTaskNumber = 0;
+    }
+
+    public boolean isQueueFull() {
+        return (mTaskNumber >= SAVE_TASK_LIMIT);
+    }
+
+    // Runs in main thread
+    public void addImage(final byte[] data, String title, long date, Location loc,
+            int width, int height, int orientation, ExifInterface exif,
+            OnMediaSavedListener l, ContentResolver resolver) {
+        if (isQueueFull()) {
+            Log.e(TAG, "Cannot add image when the queue is full");
+            return;
+        }
+        ImageSaveTask t = new ImageSaveTask(data, title, date,
+                (loc == null) ? null : new Location(loc),
+                width, height, orientation, exif, resolver, l);
+
+        mTaskNumber++;
+        if (isQueueFull()) {
+            onQueueFull();
+        }
+        t.execute();
+    }
+
+    public void addVideo(String path, long duration, ContentValues values,
+            OnMediaSavedListener l, ContentResolver resolver) {
+        // We don't set a queue limit for video saving because the file
+        // is already in the storage. Only updating the database.
+        new VideoSaveTask(path, duration, values, l, resolver).execute();
+    }
+
+    public void setListener(Listener l) {
+        mListener = l;
+        if (l == null) return;
+        l.onQueueStatus(isQueueFull());
+    }
+
+    private void onQueueFull() {
+        if (mListener != null) mListener.onQueueStatus(true);
+    }
+
+    private void onQueueAvailable() {
+        if (mListener != null) mListener.onQueueStatus(false);
+    }
+
+    private class ImageSaveTask extends AsyncTask <Void, Void, Uri> {
+        private byte[] data;
+        private String title;
+        private long date;
+        private Location loc;
+        private int width, height;
+        private int orientation;
+        private ExifInterface exif;
+        private ContentResolver resolver;
+        private OnMediaSavedListener listener;
+
+        public ImageSaveTask(byte[] data, String title, long date, Location loc,
+                             int width, int height, int orientation, ExifInterface exif,
+                             ContentResolver resolver, OnMediaSavedListener listener) {
+            this.data = data;
+            this.title = title;
+            this.date = date;
+            this.loc = loc;
+            this.width = width;
+            this.height = height;
+            this.orientation = orientation;
+            this.exif = exif;
+            this.resolver = resolver;
+            this.listener = listener;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            // do nothing.
+        }
+
+        @Override
+        protected Uri doInBackground(Void... v) {
+            return Storage.addImage(
+                    resolver, title, date, loc, orientation, exif, data, width, height);
+        }
+
+        @Override
+        protected void onPostExecute(Uri uri) {
+            if (listener != null) listener.onMediaSaved(uri);
+            mTaskNumber--;
+            if (mTaskNumber == SAVE_TASK_LIMIT - 1) onQueueAvailable();
+        }
+    }
+
+    private class VideoSaveTask extends AsyncTask <Void, Void, Uri> {
+        private String path;
+        private long duration;
+        private ContentValues values;
+        private OnMediaSavedListener listener;
+        private ContentResolver resolver;
+
+        public VideoSaveTask(String path, long duration, ContentValues values,
+                OnMediaSavedListener l, ContentResolver r) {
+            this.path = path;
+            this.duration = duration;
+            this.values = new ContentValues(values);
+            this.listener = l;
+            this.resolver = r;
+        }
+
+        @Override
+        protected void onPreExecute() {
+            // do nothing.
+        }
+
+        @Override
+        protected Uri doInBackground(Void... v) {
+            values.put(Video.Media.SIZE, new File(path).length());
+            values.put(Video.Media.DURATION, duration);
+            Uri uri = null;
+            try {
+                Uri videoTable = Uri.parse("content://media/external/video/media");
+                uri = resolver.insert(videoTable, values);
+
+                // Rename the video file to the final name. This avoids other
+                // apps reading incomplete data.  We need to do it after we are
+                // certain that the previous insert to MediaProvider is completed.
+                String finalName = values.getAsString(
+                        Video.Media.DATA);
+                if (new File(path).renameTo(new File(finalName))) {
+                    path = finalName;
+                }
+
+                resolver.update(uri, values, null, null);
+            } catch (Exception e) {
+                // We failed to insert into the database. This can happen if
+                // the SD card is unmounted.
+                Log.e(TAG, "failed to add video to media store", e);
+                uri = null;
+            } finally {
+                Log.v(TAG, "Current video URI: " + uri);
+            }
+            return uri;
+        }
+
+        @Override
+        protected void onPostExecute(Uri uri) {
+            if (listener != null) listener.onMediaSaved(uri);
+        }
+    }
+}
diff --git a/src/com/android/camera/Mosaic.java b/src/com/android/camera/Mosaic.java
new file mode 100644
index 0000000..78876c3
--- /dev/null
+++ b/src/com/android/camera/Mosaic.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * The Java interface to JNI calls regarding mosaic stitching.
+ *
+ * A high-level usage is:
+ *
+ * Mosaic mosaic = new Mosaic();
+ * mosaic.setSourceImageDimensions(width, height);
+ * mosaic.reset(blendType);
+ *
+ * while ((pixels = hasNextImage()) != null) {
+ *    mosaic.setSourceImage(pixels);
+ * }
+ *
+ * mosaic.createMosaic(highRes);
+ * byte[] result = mosaic.getFinalMosaic();
+ *
+ */
+public class Mosaic {
+    /**
+     * In this mode, the images are stitched together in the same spatial arrangement as acquired
+     * i.e. if the user follows a curvy trajectory, the image boundary of the resulting mosaic will
+     * be curved in the same manner. This mode is useful if the user wants to capture a mosaic as
+     * if "painting" the scene using the smart-phone device and does not want any corrective warps
+     * to distort the captured images.
+     */
+    public static final int BLENDTYPE_FULL = 0;
+
+    /**
+     * This mode is the same as BLENDTYPE_FULL except that the resulting mosaic is rotated
+     * to balance the first and last images to be approximately at the same vertical offset in the
+     * output mosaic. This is useful when acquiring a mosaic by a typical panning-like motion to
+     * remove a one-sided curve in the mosaic (typically due to the camera not staying horizontal
+     * during the video capture) and convert it to a more symmetrical "smiley-face" like output.
+     */
+    public static final int BLENDTYPE_PAN = 1;
+
+    /**
+     * This mode compensates for typical "smiley-face" like output in longer mosaics and creates
+     * a rectangular mosaic with minimal black borders (by unwrapping the mosaic onto an imaginary
+     * cylinder). If the user follows a curved trajectory (instead of a perfect panning trajectory),
+     * the resulting mosaic here may suffer from some image distortions in trying to map the
+     * trajectory to a cylinder.
+     */
+    public static final int BLENDTYPE_CYLINDERPAN = 2;
+
+    /**
+     * This mode is basically BLENDTYPE_CYLINDERPAN plus doing a rectangle cropping before returning
+     * the mosaic. The mode is useful for making the resulting mosaic have a rectangle shape.
+     */
+    public static final int BLENDTYPE_HORIZONTAL =3;
+
+    /**
+     * This strip type will use the default thin strips where the strips are
+     * spaced according to the image capture rate.
+     */
+    public static final int STRIPTYPE_THIN = 0;
+
+    /**
+     * This strip type will use wider strips for blending. The strip separation
+     * is controlled by a threshold on the native side. Since the strips are
+     * wider, there is an additional cross-fade blending step to make the seam
+     * boundaries smoother. Since this mode uses lesser image frames, it is
+     * computationally more efficient than the thin strip mode.
+     */
+    public static final int STRIPTYPE_WIDE = 1;
+
+    /**
+     * Return flags returned by createMosaic() are one of the following.
+     */
+    public static final int MOSAIC_RET_OK = 1;
+    public static final int MOSAIC_RET_ERROR = -1;
+    public static final int MOSAIC_RET_CANCELLED = -2;
+    public static final int MOSAIC_RET_LOW_TEXTURE = -3;
+    public static final int MOSAIC_RET_FEW_INLIERS = 2;
+
+
+    static {
+        System.loadLibrary("jni_mosaic");
+    }
+
+    /**
+     * Allocate memory for the image frames at the given resolution.
+     *
+     * @param width width of the input frames in pixels
+     * @param height height of the input frames in pixels
+     */
+    public native void allocateMosaicMemory(int width, int height);
+
+    /**
+     * Free memory allocated by allocateMosaicMemory.
+     *
+     */
+    public native void freeMosaicMemory();
+
+    /**
+     * Pass the input image frame to the native layer. Each time the a new
+     * source image t is set, the transformation matrix from the first source
+     * image to t is computed and returned.
+     *
+     * @param pixels source image of NV21 format.
+     * @return Float array of length 11; first 9 entries correspond to the 3x3
+     *         transformation matrix between the first frame and the passed frame;
+     *         the 10th entry is the number of the passed frame, where the counting
+     *         starts from 1; and the 11th entry is the returning code, whose value
+     *         is one of those MOSAIC_RET_* returning flags defined above.
+     */
+    public native float[] setSourceImage(byte[] pixels);
+
+    /**
+     * This is an alternative to the setSourceImage function above. This should
+     * be called when the image data is already on the native side in a fixed
+     * byte array. In implementation, this array is filled by the GL thread
+     * using glReadPixels directly from GPU memory (where it is accessed by
+     * an associated SurfaceTexture).
+     *
+     * @return Float array of length 11; first 9 entries correspond to the 3x3
+     *         transformation matrix between the first frame and the passed frame;
+     *         the 10th entry is the number of the passed frame, where the counting
+     *         starts from 1; and the 11th entry is the returning code, whose value
+     *         is one of those MOSAIC_RET_* returning flags defined above.
+     */
+    public native float[] setSourceImageFromGPU();
+
+    /**
+     * Set the type of blending.
+     *
+     * @param type the blending type defined in the class. {BLENDTYPE_FULL,
+     *        BLENDTYPE_PAN, BLENDTYPE_CYLINDERPAN, BLENDTYPE_HORIZONTAL}
+     */
+    public native void setBlendingType(int type);
+
+    /**
+     * Set the type of strips to use for blending.
+     * @param type the blending strip type to use {STRIPTYPE_THIN,
+     * STRIPTYPE_WIDE}.
+     */
+    public native void setStripType(int type);
+
+    /**
+     * Tell the native layer to create the final mosaic after all the input frame
+     * data have been collected.
+     * The case of generating high-resolution mosaic may take dozens of seconds to finish.
+     *
+     * @param value True means generating a high-resolution mosaic -
+     *        which is based on the original images set in setSourceImage().
+     *        False means generating a low-resolution version -
+     *        which is based on 1/4 downscaled images from the original images.
+     * @return Returns a status code suggesting if the mosaic building was
+     *        successful, in error, or was cancelled by the user.
+     */
+    public native int createMosaic(boolean value);
+
+    /**
+     * Get the data for the created mosaic.
+     *
+     * @return Returns an integer array which contains the final mosaic in the ARGB_8888 format.
+     *         The first MosaicWidth*MosaicHeight values contain the image data, followed by 2
+     *         integers corresponding to the values MosaicWidth and MosaicHeight respectively.
+     */
+    public native int[] getFinalMosaic();
+
+    /**
+     * Get the data for the created mosaic.
+     *
+     * @return Returns a byte array which contains the final mosaic in the NV21 format.
+     *         The first MosaicWidth*MosaicHeight*1.5 values contain the image data, followed by
+     *         8 bytes which pack the MosaicWidth and MosaicHeight integers into 4 bytes each
+     *         respectively.
+     */
+    public native byte[] getFinalMosaicNV21();
+
+    /**
+     * Reset the state of the frame arrays which maintain the captured frame data.
+     * Also re-initializes the native mosaic object to make it ready for capturing a new mosaic.
+     */
+    public native void reset();
+
+    /**
+     * Get the progress status of the mosaic computation process.
+     * @param hires Boolean flag to select whether to report progress of the
+     *              low-res or high-res mosaicer.
+     * @param cancelComputation Boolean flag to allow cancelling the
+     *              mosaic computation when needed from the GUI end.
+     * @return Returns a number from 0-100 where 50 denotes that the mosaic
+     *          computation is 50% done.
+     */
+    public native int reportProgress(boolean hires, boolean cancelComputation);
+}
diff --git a/src/com/android/camera/MosaicFrameProcessor.java b/src/com/android/camera/MosaicFrameProcessor.java
new file mode 100644
index 0000000..efd4ad2
--- /dev/null
+++ b/src/com/android/camera/MosaicFrameProcessor.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2011 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.util.Log;
+
+/**
+ * Class to handle the processing of each frame by Mosaicer.
+ */
+public class MosaicFrameProcessor {
+    private static final String TAG = "MosaicFrameProcessor";
+    private static final int NUM_FRAMES_IN_BUFFER = 2;
+    private static final int MAX_NUMBER_OF_FRAMES = 100;
+    private static final int MOSAIC_RET_CODE_INDEX = 10;
+    private static final int FRAME_COUNT_INDEX = 9;
+    private static final int X_COORD_INDEX = 2;
+    private static final int Y_COORD_INDEX = 5;
+    private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4;
+    private static final int WINDOW_SIZE = 3;
+
+    private Mosaic mMosaicer;
+    private boolean mIsMosaicMemoryAllocated = false;
+    private float mTranslationLastX;
+    private float mTranslationLastY;
+
+    private int mFillIn = 0;
+    private int mTotalFrameCount = 0;
+    private int mLastProcessFrameIdx = -1;
+    private int mCurrProcessFrameIdx = -1;
+    private boolean mFirstRun;
+
+    // Panning rate is in unit of percentage of image content translation per
+    // frame. Use moving average to calculate the panning rate.
+    private float mPanningRateX;
+    private float mPanningRateY;
+
+    private float[] mDeltaX = new float[WINDOW_SIZE];
+    private float[] mDeltaY = new float[WINDOW_SIZE];
+    private int mOldestIdx = 0;
+    private float mTotalTranslationX = 0f;
+    private float mTotalTranslationY = 0f;
+
+    private ProgressListener mProgressListener;
+
+    private int mPreviewWidth;
+    private int mPreviewHeight;
+    private int mPreviewBufferSize;
+
+    private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton
+
+    public interface ProgressListener {
+        public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
+                float progressX, float progressY);
+    }
+
+    public static MosaicFrameProcessor getInstance() {
+        if (sMosaicFrameProcessor == null) {
+            sMosaicFrameProcessor = new MosaicFrameProcessor();
+        }
+        return sMosaicFrameProcessor;
+    }
+
+    private MosaicFrameProcessor() {
+        mMosaicer = new Mosaic();
+    }
+
+    public void setProgressListener(ProgressListener listener) {
+        mProgressListener = listener;
+    }
+
+    public int reportProgress(boolean hires, boolean cancel) {
+        return mMosaicer.reportProgress(hires, cancel);
+    }
+
+    public void initialize(int previewWidth, int previewHeight, int bufSize) {
+        mPreviewWidth = previewWidth;
+        mPreviewHeight = previewHeight;
+        mPreviewBufferSize = bufSize;
+        setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize);
+        setStripType(Mosaic.STRIPTYPE_WIDE);
+        // no need to call reset() here. reset() should be called by the client
+        // after this initialization before calling other methods of this object.
+    }
+
+    public void clear() {
+        if (mIsMosaicMemoryAllocated) {
+            mMosaicer.freeMosaicMemory();
+            mIsMosaicMemoryAllocated = false;
+        }
+        synchronized (this) {
+            notify();
+        }
+    }
+
+    public boolean isMosaicMemoryAllocated() {
+        return mIsMosaicMemoryAllocated;
+    }
+
+    public void setStripType(int type) {
+        mMosaicer.setStripType(type);
+    }
+
+    private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) {
+        Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize);
+
+        if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!");
+        mIsMosaicMemoryAllocated = true;
+        mMosaicer.allocateMosaicMemory(previewWidth, previewHeight);
+    }
+
+    public void reset() {
+        // reset() can be called even if MosaicFrameProcessor is not initialized.
+        // Only counters will be changed.
+        mFirstRun = true;
+        mTotalFrameCount = 0;
+        mFillIn = 0;
+        mTotalTranslationX = 0;
+        mTranslationLastX = 0;
+        mTotalTranslationY = 0;
+        mTranslationLastY = 0;
+        mPanningRateX = 0;
+        mPanningRateY = 0;
+        mLastProcessFrameIdx = -1;
+        mCurrProcessFrameIdx = -1;
+        for (int i = 0; i < WINDOW_SIZE; ++i) {
+            mDeltaX[i] = 0f;
+            mDeltaY[i] = 0f;
+        }
+        mMosaicer.reset();
+    }
+
+    public int createMosaic(boolean highRes) {
+        return mMosaicer.createMosaic(highRes);
+    }
+
+    public byte[] getFinalMosaicNV21() {
+        return mMosaicer.getFinalMosaicNV21();
+    }
+
+    // Processes the last filled image frame through the mosaicer and
+    // updates the UI to show progress.
+    // When done, processes and displays the final mosaic.
+    public void processFrame() {
+        if (!mIsMosaicMemoryAllocated) {
+            // clear() is called and buffers are cleared, stop computation.
+            // This can happen when the onPause() is called in the activity, but still some frames
+            // are not processed yet and thus the callback may be invoked.
+            return;
+        }
+
+        mCurrProcessFrameIdx = mFillIn;
+        mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER);
+
+        // Check that we are trying to process a frame different from the
+        // last one processed (useful if this class was running asynchronously)
+        if (mCurrProcessFrameIdx != mLastProcessFrameIdx) {
+            mLastProcessFrameIdx = mCurrProcessFrameIdx;
+
+            // TODO: make the termination condition regarding reaching
+            // MAX_NUMBER_OF_FRAMES solely determined in the library.
+            if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) {
+                // If we are still collecting new frames for the current mosaic,
+                // process the new frame.
+                calculateTranslationRate();
+
+                // Publish progress of the ongoing processing
+                if (mProgressListener != null) {
+                    mProgressListener.onProgress(false, mPanningRateX, mPanningRateY,
+                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
+                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
+                }
+            } else {
+                if (mProgressListener != null) {
+                    mProgressListener.onProgress(true, mPanningRateX, mPanningRateY,
+                            mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth,
+                            mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight);
+                }
+            }
+        }
+    }
+
+    public void calculateTranslationRate() {
+        float[] frameData = mMosaicer.setSourceImageFromGPU();
+        int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX];
+        mTotalFrameCount  = (int) frameData[FRAME_COUNT_INDEX];
+        float translationCurrX = frameData[X_COORD_INDEX];
+        float translationCurrY = frameData[Y_COORD_INDEX];
+
+        if (mFirstRun) {
+            // First time: no need to update delta values.
+            mTranslationLastX = translationCurrX;
+            mTranslationLastY = translationCurrY;
+            mFirstRun = false;
+            return;
+        }
+
+        // Moving average: remove the oldest translation/deltaTime and
+        // add the newest translation/deltaTime in
+        int idx = mOldestIdx;
+        mTotalTranslationX -= mDeltaX[idx];
+        mTotalTranslationY -= mDeltaY[idx];
+        mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX);
+        mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY);
+        mTotalTranslationX += mDeltaX[idx];
+        mTotalTranslationY += mDeltaY[idx];
+
+        // The panning rate is measured as the rate of the translation percentage in
+        // image width/height. Take the horizontal panning rate for example, the image width
+        // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR).
+        // To get the horizontal translation percentage, the horizontal translation,
+        // (translationCurrX - mTranslationLastX), is divided by the
+        // image width. We then get the rate by dividing the translation percentage with the
+        // number of frames.
+        mPanningRateX = mTotalTranslationX /
+                (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
+        mPanningRateY = mTotalTranslationY /
+                (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE;
+
+        mTranslationLastX = translationCurrX;
+        mTranslationLastY = translationCurrY;
+        mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE;
+    }
+}
diff --git a/src/com/android/camera/MosaicPreviewRenderer.java b/src/com/android/camera/MosaicPreviewRenderer.java
new file mode 100644
index 0000000..26ce733
--- /dev/null
+++ b/src/com/android/camera/MosaicPreviewRenderer.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2011 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.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
+public class MosaicPreviewRenderer {
+    private static final String TAG = "MosaicPreviewRenderer";
+    private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+    private static final boolean DEBUG = false;
+
+    private int mWidth; // width of the view in UI
+    private int mHeight; // height of the view in UI
+
+    private boolean mIsLandscape = true;
+    private final float[] mTransformMatrix = new float[16];
+
+    private ConditionVariable mEglThreadBlockVar = new ConditionVariable();
+    private HandlerThread mEglThread;
+    private EGLHandler mEglHandler;
+
+    private EGLConfig mEglConfig;
+    private EGLDisplay mEglDisplay;
+    private EGLContext mEglContext;
+    private EGLSurface mEglSurface;
+    private SurfaceTexture mMosaicOutputSurfaceTexture;
+    private SurfaceTexture mInputSurfaceTexture;
+    private EGL10 mEgl;
+    private GL10 mGl;
+
+    private class EGLHandler extends Handler {
+        public static final int MSG_INIT_EGL_SYNC = 0;
+        public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1;
+        public static final int MSG_SHOW_PREVIEW_FRAME = 2;
+        public static final int MSG_ALIGN_FRAME_SYNC = 3;
+        public static final int MSG_RELEASE = 4;
+
+        public EGLHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_INIT_EGL_SYNC:
+                    doInitGL();
+                    mEglThreadBlockVar.open();
+                    break;
+                case MSG_SHOW_PREVIEW_FRAME_SYNC:
+                    doShowPreviewFrame();
+                    mEglThreadBlockVar.open();
+                    break;
+                case MSG_SHOW_PREVIEW_FRAME:
+                    doShowPreviewFrame();
+                    break;
+                case MSG_ALIGN_FRAME_SYNC:
+                    doAlignFrame();
+                    mEglThreadBlockVar.open();
+                    break;
+                case MSG_RELEASE:
+                    doRelease();
+                    mEglThreadBlockVar.open();
+                    break;
+            }
+        }
+
+        private void doAlignFrame() {
+            mInputSurfaceTexture.updateTexImage();
+            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
+
+            MosaicRenderer.setWarping(true);
+            // Call preprocess to render it to low-res and high-res RGB textures.
+            MosaicRenderer.preprocess(mTransformMatrix);
+            // Now, transfer the textures from GPU to CPU memory for processing
+            MosaicRenderer.transferGPUtoCPU();
+            MosaicRenderer.updateMatrix();
+            draw();
+            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+        }
+
+        private void doShowPreviewFrame() {
+            mInputSurfaceTexture.updateTexImage();
+            mInputSurfaceTexture.getTransformMatrix(mTransformMatrix);
+
+            MosaicRenderer.setWarping(false);
+            // Call preprocess to render it to low-res and high-res RGB textures.
+            MosaicRenderer.preprocess(mTransformMatrix);
+            MosaicRenderer.updateMatrix();
+            draw();
+            mEgl.eglSwapBuffers(mEglDisplay, mEglSurface);
+        }
+
+        private void doInitGL() {
+            // These are copied from GLSurfaceView
+            mEgl = (EGL10) EGLContext.getEGL();
+            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+            int[] version = new int[2];
+            if (!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            } else {
+                Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]);
+            }
+            int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+            mEglConfig = chooseConfig(mEgl, mEglDisplay);
+            mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT,
+                                                attribList);
+
+            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+                throw new RuntimeException("failed to createContext");
+            }
+            mEglSurface = mEgl.eglCreateWindowSurface(
+                    mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null);
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                throw new RuntimeException("failed to createWindowSurface");
+            }
+
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                throw new RuntimeException("failed to eglMakeCurrent");
+            }
+
+            mGl = (GL10) mEglContext.getGL();
+
+            mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init());
+            MosaicRenderer.reset(mWidth, mHeight, mIsLandscape);
+        }
+
+        private void doRelease() {
+            mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+            mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+            mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
+                    EGL10.EGL_NO_CONTEXT);
+            mEgl.eglTerminate(mEglDisplay);
+            mEglSurface = null;
+            mEglContext = null;
+            mEglDisplay = null;
+            releaseSurfaceTexture(mInputSurfaceTexture);
+            mEglThread.quit();
+        }
+
+        @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+        private void releaseSurfaceTexture(SurfaceTexture st) {
+            if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) {
+                st.release();
+            }
+        }
+
+        // Should be called from other thread.
+        public void sendMessageSync(int msg) {
+            mEglThreadBlockVar.close();
+            sendEmptyMessage(msg);
+            mEglThreadBlockVar.block();
+        }
+
+    }
+
+    public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) {
+        mMosaicOutputSurfaceTexture = tex;
+        mWidth = w;
+        mHeight = h;
+        mIsLandscape = isLandscape;
+
+        mEglThread = new HandlerThread("PanoramaRealtimeRenderer");
+        mEglThread.start();
+        mEglHandler = new EGLHandler(mEglThread.getLooper());
+
+        // We need to sync this because the generation of surface texture for input is
+        // done here and the client will continue with the assumption that the
+        // generation is completed.
+        mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC);
+    }
+
+    public void release() {
+        mEglHandler.sendMessageSync(EGLHandler.MSG_RELEASE);
+    }
+
+    public void showPreviewFrameSync() {
+        mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC);
+    }
+
+    public void showPreviewFrame() {
+        mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME);
+    }
+
+    public void alignFrameSync() {
+        mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC);
+    }
+
+    public SurfaceTexture getInputSurfaceTexture() {
+        return mInputSurfaceTexture;
+    }
+
+    private void draw() {
+        MosaicRenderer.step();
+    }
+
+    private static void checkEglError(String prompt, EGL10 egl) {
+        int error;
+        while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+            Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
+        }
+    }
+
+    private static final int EGL_OPENGL_ES2_BIT = 4;
+    private static final int[] CONFIG_SPEC = new int[] {
+            EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL10.EGL_RED_SIZE, 8,
+            EGL10.EGL_GREEN_SIZE, 8,
+            EGL10.EGL_BLUE_SIZE, 8,
+            EGL10.EGL_NONE
+    };
+
+    private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+        int[] numConfig = new int[1];
+        if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) {
+            throw new IllegalArgumentException("eglChooseConfig failed");
+        }
+
+        int numConfigs = numConfig[0];
+        if (numConfigs <= 0) {
+            throw new IllegalArgumentException("No configs match configSpec");
+        }
+
+        EGLConfig[] configs = new EGLConfig[numConfigs];
+        if (!egl.eglChooseConfig(
+                display, CONFIG_SPEC, configs, numConfigs, numConfig)) {
+            throw new IllegalArgumentException("eglChooseConfig#2 failed");
+        }
+
+        return configs[0];
+    }
+}
diff --git a/src/com/android/camera/MosaicRenderer.java b/src/com/android/camera/MosaicRenderer.java
new file mode 100644
index 0000000..c50ca0d
--- /dev/null
+++ b/src/com/android/camera/MosaicRenderer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * The Java interface to JNI calls regarding mosaic preview rendering.
+ *
+ */
+public class MosaicRenderer
+{
+     static
+     {
+         System.loadLibrary("jni_mosaic");
+     }
+
+     /**
+      * Function to be called in onSurfaceCreated() to initialize
+      * the GL context, load and link the shaders and create the
+      * program. Returns a texture ID to be used for SurfaceTexture.
+      *
+      * @return textureID the texture ID of the newly generated texture to
+      *          be assigned to the SurfaceTexture object.
+      */
+     public static native int init();
+
+     /**
+      * Pass the drawing surface's width and height to initialize the
+      * renderer viewports and FBO dimensions.
+      *
+      * @param width width of the drawing surface in pixels.
+      * @param height height of the drawing surface in pixels.
+      * @param isLandscapeOrientation is the orientation of the activity layout in landscape.
+      */
+     public static native void reset(int width, int height, boolean isLandscapeOrientation);
+
+     /**
+      * Calling this function will render the SurfaceTexture to a new 2D texture
+      * using the provided STMatrix.
+      *
+      * @param stMatrix texture coordinate transform matrix obtained from the
+      *        Surface texture
+      */
+     public static native void preprocess(float[] stMatrix);
+
+     /**
+      * This function calls glReadPixels to transfer both the low-res and high-res
+      * data from the GPU memory to the CPU memory for further processing by the
+      * mosaicing library.
+      */
+     public static native void transferGPUtoCPU();
+
+     /**
+      * Function to be called in onDrawFrame() to update the screen with
+      * the new frame data.
+      */
+     public static native void step();
+
+     /**
+      * Call this function when a new low-res frame has been processed by
+      * the mosaicing library. This will tell the renderer library to
+      * update its texture and warping transformation. Any calls to step()
+      * after this call will use the new image frame and transformation data.
+      */
+     public static native void updateMatrix();
+
+     /**
+      * This function allows toggling between showing the input image data
+      * (without applying any warp) and the warped image data. For running
+      * the renderer as a viewfinder, we set the flag to false. To see the
+      * preview mosaic, we set the flag to true.
+      *
+      * @param flag boolean flag to set the warping to true or false.
+      */
+     public static native void setWarping(boolean flag);
+}
diff --git a/src/com/android/camera/OnClickAttr.java b/src/com/android/camera/OnClickAttr.java
new file mode 100644
index 0000000..07a1063
--- /dev/null
+++ b/src/com/android/camera/OnClickAttr.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 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 java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Interface for OnClickAttr annotation.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface OnClickAttr {
+}
diff --git a/src/com/android/camera/OnScreenHint.java b/src/com/android/camera/OnScreenHint.java
new file mode 100644
index 0000000..4d7fa70
--- /dev/null
+++ b/src/com/android/camera/OnScreenHint.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+
+/**
+ * A on-screen hint is a view containing a little message for the user and will
+ * be shown on the screen continuously.  This class helps you create and show
+ * those.
+ *
+ * <p>
+ * When the view is shown to the user, appears as a floating view over the
+ * application.
+ * <p>
+ * The easiest way to use this class is to call one of the static methods that
+ * constructs everything you need and returns a new {@code OnScreenHint} object.
+ */
+public class OnScreenHint {
+    static final String TAG = "OnScreenHint";
+
+    int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+    int mX, mY;
+    float mHorizontalMargin;
+    float mVerticalMargin;
+    View mView;
+    View mNextView;
+
+    private final WindowManager.LayoutParams mParams =
+            new WindowManager.LayoutParams();
+    private final WindowManager mWM;
+    private final Handler mHandler = new Handler();
+
+    /**
+     * Construct an empty OnScreenHint object.
+     *
+     * @param context  The context to use.  Usually your
+     *                 {@link android.app.Application} or
+     *                 {@link android.app.Activity} object.
+     */
+    private OnScreenHint(Context context) {
+        mWM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        mY = context.getResources().getDimensionPixelSize(
+                R.dimen.hint_y_offset);
+
+        mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        mParams.format = PixelFormat.TRANSLUCENT;
+        mParams.windowAnimations = R.style.Animation_OnScreenHint;
+        mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+        mParams.setTitle("OnScreenHint");
+    }
+
+    /**
+     * Show the view on the screen.
+     */
+    public void show() {
+        if (mNextView == null) {
+            throw new RuntimeException("View is not initialized");
+        }
+        mHandler.post(mShow);
+    }
+
+    /**
+     * Close the view if it's showing.
+     */
+    public void cancel() {
+        mHandler.post(mHide);
+    }
+
+    /**
+     * Make a standard hint that just contains a text view.
+     *
+     * @param context  The context to use.  Usually your
+     *                 {@link android.app.Application} or
+     *                 {@link android.app.Activity} object.
+     * @param text     The text to show.  Can be formatted text.
+     *
+     */
+    public static OnScreenHint makeText(Context context, CharSequence text) {
+        OnScreenHint result = new OnScreenHint(context);
+
+        LayoutInflater inflate =
+                (LayoutInflater) context.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        View v = inflate.inflate(R.layout.on_screen_hint, null);
+        TextView tv = (TextView) v.findViewById(R.id.message);
+        tv.setText(text);
+
+        result.mNextView = v;
+
+        return result;
+    }
+
+    /**
+     * Update the text in a OnScreenHint that was previously created using one
+     * of the makeText() methods.
+     * @param s The new text for the OnScreenHint.
+     */
+    public void setText(CharSequence s) {
+        if (mNextView == null) {
+            throw new RuntimeException("This OnScreenHint was not "
+                    + "created with OnScreenHint.makeText()");
+        }
+        TextView tv = (TextView) mNextView.findViewById(R.id.message);
+        if (tv == null) {
+            throw new RuntimeException("This OnScreenHint was not "
+                    + "created with OnScreenHint.makeText()");
+        }
+        tv.setText(s);
+    }
+
+    private synchronized void handleShow() {
+        if (mView != mNextView) {
+            // remove the old view if necessary
+            handleHide();
+            mView = mNextView;
+            final int gravity = mGravity;
+            mParams.gravity = gravity;
+            if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK)
+                    == Gravity.FILL_HORIZONTAL) {
+                mParams.horizontalWeight = 1.0f;
+            }
+            if ((gravity & Gravity.VERTICAL_GRAVITY_MASK)
+                    == Gravity.FILL_VERTICAL) {
+                mParams.verticalWeight = 1.0f;
+            }
+            mParams.x = mX;
+            mParams.y = mY;
+            mParams.verticalMargin = mVerticalMargin;
+            mParams.horizontalMargin = mHorizontalMargin;
+            if (mView.getParent() != null) {
+                mWM.removeView(mView);
+            }
+            mWM.addView(mView, mParams);
+        }
+    }
+
+    private synchronized void handleHide() {
+        if (mView != null) {
+            // note: checking parent() just to make sure the view has
+            // been added...  i have seen cases where we get here when
+            // the view isn't yet added, so let's try not to crash.
+            if (mView.getParent() != null) {
+                mWM.removeView(mView);
+            }
+            mView = null;
+        }
+    }
+
+    private final Runnable mShow = new Runnable() {
+        @Override
+        public void run() {
+            handleShow();
+        }
+    };
+
+    private final Runnable mHide = new Runnable() {
+        @Override
+        public void run() {
+            handleHide();
+        }
+    };
+}
+
diff --git a/src/com/android/camera/OnScreenIndicators.java b/src/com/android/camera/OnScreenIndicators.java
new file mode 100644
index 0000000..77c8faf
--- /dev/null
+++ b/src/com/android/camera/OnScreenIndicators.java
@@ -0,0 +1,190 @@
+/*
+ * 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.Context;
+import android.content.res.TypedArray;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.gallery3d.R;
+
+/**
+ * The on-screen indicators of the pie menu button. They show the camera
+ * settings in the viewfinder.
+ */
+public class OnScreenIndicators {
+    private final int[] mWBArray;
+    private final View mOnScreenIndicators;
+    private final ImageView mExposureIndicator;
+    private final ImageView mFlashIndicator;
+    private final ImageView mSceneIndicator;
+    private final ImageView mLocationIndicator;
+    private final ImageView mTimerIndicator;
+    private final ImageView mWBIndicator;
+
+    public OnScreenIndicators(Context ctx, View onScreenIndicatorsView) {
+        TypedArray iconIds = ctx.getResources().obtainTypedArray(
+                R.array.camera_wb_indicators);
+        final int n = iconIds.length();
+        mWBArray = new int[n];
+        for (int i = 0; i < n; i++) {
+            mWBArray[i] = iconIds.getResourceId(i, R.drawable.ic_indicator_wb_off);
+        }
+        mOnScreenIndicators = onScreenIndicatorsView;
+        mExposureIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_exposure_indicator);
+        mFlashIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_flash_indicator);
+        mSceneIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_scenemode_indicator);
+        mLocationIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_location_indicator);
+        mTimerIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_timer_indicator);
+        mWBIndicator = (ImageView) onScreenIndicatorsView.findViewById(
+                R.id.menu_wb_indicator);
+    }
+
+    /**
+     * Resets all indicators to show the default values.
+     */
+    public void resetToDefault() {
+        updateExposureOnScreenIndicator(0);
+        updateFlashOnScreenIndicator(Parameters.FLASH_MODE_OFF);
+        updateSceneOnScreenIndicator(Parameters.SCENE_MODE_AUTO);
+        updateWBIndicator(2);
+        updateTimerIndicator(false);
+        updateLocationIndicator(false);
+    }
+
+    /**
+     * Sets the exposure indicator using exposure compensations step rounding.
+     */
+    public void updateExposureOnScreenIndicator(Camera.Parameters params, int value) {
+        if (mExposureIndicator == null) {
+            return;
+        }
+        float step = params.getExposureCompensationStep();
+        value = Math.round(value * step);
+        updateExposureOnScreenIndicator(value);
+    }
+
+    /**
+     * Set the exposure indicator to the given value.
+     *
+     * @param value Value between -3 and 3. If outside this range, 0 is used by
+     *            default.
+     */
+    public void updateExposureOnScreenIndicator(int value) {
+        int id = 0;
+        switch(value) {
+        case -3:
+            id = R.drawable.ic_indicator_ev_n3;
+            break;
+        case -2:
+            id = R.drawable.ic_indicator_ev_n2;
+            break;
+        case -1:
+            id = R.drawable.ic_indicator_ev_n1;
+            break;
+        case 0:
+            id = R.drawable.ic_indicator_ev_0;
+            break;
+        case 1:
+            id = R.drawable.ic_indicator_ev_p1;
+            break;
+        case 2:
+            id = R.drawable.ic_indicator_ev_p2;
+            break;
+        case 3:
+            id = R.drawable.ic_indicator_ev_p3;
+            break;
+        }
+        mExposureIndicator.setImageResource(id);
+    }
+
+    public void updateWBIndicator(int wbIndex) {
+        if (mWBIndicator == null) return;
+        mWBIndicator.setImageResource(mWBArray[wbIndex]);
+    }
+
+    public void updateTimerIndicator(boolean on) {
+        if (mTimerIndicator == null) return;
+        mTimerIndicator.setImageResource(on ? R.drawable.ic_indicator_timer_on
+                : R.drawable.ic_indicator_timer_off);
+    }
+
+    public void updateLocationIndicator(boolean on) {
+        if (mLocationIndicator == null) return;
+        mLocationIndicator.setImageResource(on ? R.drawable.ic_indicator_loc_on
+                : R.drawable.ic_indicator_loc_off);
+    }
+
+    /**
+     * Set the flash indicator to the given value.
+     *
+     * @param value One of Parameters.FLASH_MODE_OFF,
+     *            Parameters.FLASH_MODE_AUTO, Parameters.FLASH_MODE_ON.
+     */
+    public void updateFlashOnScreenIndicator(String value) {
+        if (mFlashIndicator == null) {
+            return;
+        }
+        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
+            mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+        } else {
+            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
+            } else if (Parameters.FLASH_MODE_ON.equals(value)
+                    || Parameters.FLASH_MODE_TORCH.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
+            } else {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+            }
+        }
+    }
+
+    /**
+     * Set the scene indicator depending on the given scene mode.
+     *
+     * @param value the current Parameters.SCENE_MODE_* value.
+     */
+    public void updateSceneOnScreenIndicator(String value) {
+        if (mSceneIndicator == null) {
+            return;
+        }
+        if ((value == null) || Parameters.SCENE_MODE_AUTO.equals(value)) {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_off);
+        } else if (Parameters.SCENE_MODE_HDR.equals(value)) {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_hdr);
+        } else {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_on);
+        }
+    }
+
+    /**
+     * Sets the visibility of all indicators.
+     *
+     * @param visibility View.VISIBLE, View.GONE etc.
+     */
+    public void setVisibility(int visibility) {
+        mOnScreenIndicators.setVisibility(visibility);
+    }
+}
diff --git a/src/com/android/camera/PanoProgressBar.java b/src/com/android/camera/PanoProgressBar.java
new file mode 100644
index 0000000..8dfb366
--- /dev/null
+++ b/src/com/android/camera/PanoProgressBar.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2011 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.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+class PanoProgressBar extends ImageView {
+    @SuppressWarnings("unused")
+    private static final String TAG = "PanoProgressBar";
+    public static final int DIRECTION_NONE = 0;
+    public static final int DIRECTION_LEFT = 1;
+    public static final int DIRECTION_RIGHT = 2;
+    private float mProgress = 0;
+    private float mMaxProgress = 0;
+    private float mLeftMostProgress = 0;
+    private float mRightMostProgress = 0;
+    private float mProgressOffset = 0;
+    private float mIndicatorWidth = 0;
+    private int mDirection = 0;
+    private final Paint mBackgroundPaint = new Paint();
+    private final Paint mDoneAreaPaint = new Paint();
+    private final Paint mIndicatorPaint = new Paint();
+    private float mWidth;
+    private float mHeight;
+    private RectF mDrawBounds;
+    private OnDirectionChangeListener mListener = null;
+
+    public interface OnDirectionChangeListener {
+        public void onDirectionChange(int direction);
+    }
+
+    public PanoProgressBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDoneAreaPaint.setStyle(Paint.Style.FILL);
+        mDoneAreaPaint.setAlpha(0xff);
+
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
+        mBackgroundPaint.setAlpha(0xff);
+
+        mIndicatorPaint.setStyle(Paint.Style.FILL);
+        mIndicatorPaint.setAlpha(0xff);
+
+        mDrawBounds = new RectF();
+    }
+
+    public void setOnDirectionChangeListener(OnDirectionChangeListener l) {
+        mListener = l;
+    }
+
+    private void setDirection(int direction) {
+        if (mDirection != direction) {
+            mDirection = direction;
+            if (mListener != null) {
+                mListener.onDirectionChange(mDirection);
+            }
+            invalidate();
+        }
+    }
+
+    public int getDirection() {
+        return mDirection;
+    }
+
+    @Override
+    public void setBackgroundColor(int color) {
+        mBackgroundPaint.setColor(color);
+        invalidate();
+    }
+
+    public void setDoneColor(int color) {
+        mDoneAreaPaint.setColor(color);
+        invalidate();
+    }
+
+    public void setIndicatorColor(int color) {
+        mIndicatorPaint.setColor(color);
+        invalidate();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+        mHeight = h;
+        mDrawBounds.set(0, 0, mWidth, mHeight);
+    }
+
+    public void setMaxProgress(int progress) {
+        mMaxProgress = progress;
+    }
+
+    public void setIndicatorWidth(float w) {
+        mIndicatorWidth = w;
+        invalidate();
+    }
+
+    public void setRightIncreasing(boolean rightIncreasing) {
+        if (rightIncreasing) {
+            mLeftMostProgress = 0;
+            mRightMostProgress = 0;
+            mProgressOffset = 0;
+            setDirection(DIRECTION_RIGHT);
+        } else {
+            mLeftMostProgress = mWidth;
+            mRightMostProgress = mWidth;
+            mProgressOffset = mWidth;
+            setDirection(DIRECTION_LEFT);
+        }
+        invalidate();
+    }
+
+    public void setProgress(int progress) {
+        // The panning direction will be decided after user pan more than 10 degrees in one
+        // direction.
+        if (mDirection == DIRECTION_NONE) {
+            if (progress > 10) {
+                setRightIncreasing(true);
+            } else if (progress < -10) {
+                setRightIncreasing(false);
+            }
+        }
+        // mDirection might be modified by setRightIncreasing() above. Need to check again.
+        if (mDirection != DIRECTION_NONE) {
+            mProgress = progress * mWidth / mMaxProgress + mProgressOffset;
+            // value bounds.
+            mProgress = Math.min(mWidth, Math.max(0, mProgress));
+            if (mDirection == DIRECTION_RIGHT) {
+                // The right most progress is adjusted.
+                mRightMostProgress = Math.max(mRightMostProgress, mProgress);
+            }
+            if (mDirection == DIRECTION_LEFT) {
+                // The left most progress is adjusted.
+                mLeftMostProgress = Math.min(mLeftMostProgress, mProgress);
+            }
+            invalidate();
+        }
+    }
+
+    public void reset() {
+        mProgress = 0;
+        mProgressOffset = 0;
+        setDirection(DIRECTION_NONE);
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // the background
+        canvas.drawRect(mDrawBounds, mBackgroundPaint);
+        if (mDirection != DIRECTION_NONE) {
+            // the progress area
+            canvas.drawRect(mLeftMostProgress, mDrawBounds.top, mRightMostProgress,
+                    mDrawBounds.bottom, mDoneAreaPaint);
+            // the indication bar
+            float l;
+            float r;
+            if (mDirection == DIRECTION_RIGHT) {
+                l = Math.max(mProgress - mIndicatorWidth, 0f);
+                r = mProgress;
+            } else {
+                l = mProgress;
+                r = Math.min(mProgress + mIndicatorWidth, mWidth);
+            }
+            canvas.drawRect(l, mDrawBounds.top, r, mDrawBounds.bottom, mIndicatorPaint);
+        }
+
+        // draw the mask image on the top for shaping.
+        super.onDraw(canvas);
+    }
+}
diff --git a/src/com/android/camera/PanoUtil.java b/src/com/android/camera/PanoUtil.java
new file mode 100644
index 0000000..e50eacc
--- /dev/null
+++ b/src/com/android/camera/PanoUtil.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2011 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 java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class PanoUtil {
+    public static String createName(String format, long dateTaken) {
+        Date date = new Date(dateTaken);
+        SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+        return dateFormat.format(date);
+    }
+
+    // TODO: Add comments about the range of these two arguments.
+    public static double calculateDifferenceBetweenAngles(double firstAngle,
+            double secondAngle) {
+        double difference1 = (secondAngle - firstAngle) % 360;
+        if (difference1 < 0) {
+            difference1 += 360;
+        }
+
+        double difference2 = (firstAngle - secondAngle) % 360;
+        if (difference2 < 0) {
+            difference2 += 360;
+        }
+
+        return Math.min(difference1, difference2);
+    }
+
+    public static void decodeYUV420SPQuarterRes(int[] rgb, byte[] yuv420sp, int width, int height) {
+        final int frameSize = width * height;
+
+        for (int j = 0, ypd = 0; j < height; j += 4) {
+            int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
+            for (int i = 0; i < width; i += 4, ypd++) {
+                int y = (0xff & (yuv420sp[j * width + i])) - 16;
+                if (y < 0) {
+                    y = 0;
+                }
+                if ((i & 1) == 0) {
+                    v = (0xff & yuv420sp[uvp++]) - 128;
+                    u = (0xff & yuv420sp[uvp++]) - 128;
+                    uvp += 2;  // Skip the UV values for the 4 pixels skipped in between
+                }
+                int y1192 = 1192 * y;
+                int r = (y1192 + 1634 * v);
+                int g = (y1192 - 833 * v - 400 * u);
+                int b = (y1192 + 2066 * u);
+
+                if (r < 0) {
+                    r = 0;
+                } else if (r > 262143) {
+                    r = 262143;
+                }
+                if (g < 0) {
+                    g = 0;
+                } else if (g > 262143) {
+                    g = 262143;
+                }
+                if (b < 0) {
+                    b = 0;
+                } else if (b > 262143) {
+                    b = 262143;
+                }
+
+                rgb[ypd] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) |
+                        ((b >> 10) & 0xff);
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java
new file mode 100644
index 0000000..007ea7a
--- /dev/null
+++ b/src/com/android/camera/PanoramaModule.java
@@ -0,0 +1,1304 @@
+/*
+ * Copyright (C) 2011 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.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.graphics.YuvImage;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.LayoutChangeNotifier;
+import com.android.camera.ui.LayoutNotifyView;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.Rotatable;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.ui.GLRootView;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * Activity to handle panorama capturing.
+ */
+@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
+public class PanoramaModule implements CameraModule,
+        SurfaceTexture.OnFrameAvailableListener,
+        ShutterButton.OnShutterButtonListener,
+        LayoutChangeNotifier.Listener {
+
+    public static final int DEFAULT_SWEEP_ANGLE = 160;
+    public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
+    public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
+
+    private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
+    private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2;
+    private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3;
+    private static final int MSG_CLEAR_SCREEN_DELAY = 4;
+    private static final int MSG_CONFIG_MOSAIC_PREVIEW = 5;
+    private static final int MSG_RESET_TO_PREVIEW = 6;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private static final String TAG = "CAM PanoModule";
+    private static final int PREVIEW_STOPPED = 0;
+    private static final int PREVIEW_ACTIVE = 1;
+    private static final int CAPTURE_STATE_VIEWFINDER = 0;
+    private static final int CAPTURE_STATE_MOSAIC = 1;
+    // The unit of speed is degrees per frame.
+    private static final float PANNING_SPEED_THRESHOLD = 2.5f;
+
+    private ContentResolver mContentResolver;
+
+    private GLRootView mGLRootView;
+    private ViewGroup mPanoLayout;
+    private LinearLayout mCaptureLayout;
+    private View mReviewLayout;
+    private ImageView mReview;
+    private View mCaptureIndicator;
+    private PanoProgressBar mPanoProgressBar;
+    private PanoProgressBar mSavingProgressBar;
+    private Matrix mProgressDirectionMatrix = new Matrix();
+    private float[] mProgressAngle = new float[2];
+    private LayoutNotifyView mPreviewArea;
+    private View mLeftIndicator;
+    private View mRightIndicator;
+    private MosaicPreviewRenderer mMosaicPreviewRenderer;
+    private Object mRendererLock = new Object();
+    private TextView mTooFastPrompt;
+    private ShutterButton mShutterButton;
+    private Object mWaitObject = new Object();
+
+    private String mPreparePreviewString;
+    private String mDialogTitle;
+    private String mDialogOkString;
+    private String mDialogPanoramaFailedString;
+    private String mDialogWaitingPreviousString;
+
+    private int mIndicatorColor;
+    private int mIndicatorColorFast;
+    private int mReviewBackground;
+
+    private boolean mUsingFrontCamera;
+    private int mPreviewWidth;
+    private int mPreviewHeight;
+    private int mCameraState;
+    private int mCaptureState;
+    private PowerManager.WakeLock mPartialWakeLock;
+    private MosaicFrameProcessor mMosaicFrameProcessor;
+    private boolean mMosaicFrameProcessorInitialized;
+    private AsyncTask <Void, Void, Void> mWaitProcessorTask;
+    private long mTimeTaken;
+    private Handler mMainHandler;
+    private SurfaceTexture mCameraTexture;
+    private boolean mThreadRunning;
+    private boolean mCancelComputation;
+    private float mHorizontalViewAngle;
+    private float mVerticalViewAngle;
+
+    // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
+    // getting a better image quality by the former.
+    private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
+
+    private PanoOrientationEventListener mOrientationEventListener;
+    // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
+    // respectively.
+    private int mDeviceOrientation;
+    private int mDeviceOrientationAtCapture;
+    private int mCameraOrientation;
+    private int mOrientationCompensation;
+
+    private RotateDialogController mRotateDialog;
+
+    private SoundClips.Player mSoundPlayer;
+
+    private Runnable mOnFrameAvailableRunnable;
+
+    private CameraActivity mActivity;
+    private View mRootView;
+    private CameraProxy mCameraDevice;
+    private boolean mPaused;
+    private boolean mIsCreatingRenderer;
+
+    private LocationManager mLocationManager;
+    private ComboPreferences mPreferences;
+
+    private class MosaicJpeg {
+        public MosaicJpeg(byte[] data, int width, int height) {
+            this.data = data;
+            this.width = width;
+            this.height = height;
+            this.isValid = true;
+        }
+
+        public MosaicJpeg() {
+            this.data = null;
+            this.width = 0;
+            this.height = 0;
+            this.isValid = false;
+        }
+
+        public final byte[] data;
+        public final int width;
+        public final int height;
+        public final boolean isValid;
+    }
+
+    private class PanoOrientationEventListener extends OrientationEventListener {
+        public PanoOrientationEventListener(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onOrientationChanged(int orientation) {
+            // We keep the last known orientation. So if the user first orient
+            // the camera then point the camera to floor or sky, we still have
+            // the correct orientation.
+            if (orientation == ORIENTATION_UNKNOWN) return;
+            mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation);
+            // When the screen is unlocked, display rotation may change. Always
+            // calculate the up-to-date orientationCompensation.
+            int orientationCompensation = mDeviceOrientation
+                    + Util.getDisplayRotation(mActivity) % 360;
+            if (mOrientationCompensation != orientationCompensation) {
+                mOrientationCompensation = orientationCompensation;
+                mActivity.getGLRoot().requestLayoutContentPane();
+            }
+        }
+    }
+
+    @Override
+    public void init(CameraActivity activity, View parent, boolean reuseScreenNail) {
+        mActivity = activity;
+        mRootView = parent;
+
+        createContentView();
+
+        mContentResolver = mActivity.getContentResolver();
+        if (reuseScreenNail) {
+            mActivity.reuseCameraScreenNail(true);
+        } else {
+            mActivity.createCameraScreenNail(true);
+        }
+
+        // This runs in UI thread.
+        mOnFrameAvailableRunnable = new Runnable() {
+            @Override
+            public void run() {
+                // Frames might still be available after the activity is paused.
+                // If we call onFrameAvailable after pausing, the GL thread will crash.
+                if (mPaused) return;
+
+                MosaicPreviewRenderer renderer = null;
+                synchronized (mRendererLock) {
+                    if (mMosaicPreviewRenderer == null) {
+                        return;
+                    }
+                    renderer = mMosaicPreviewRenderer;
+                }
+                if (mGLRootView.getVisibility() != View.VISIBLE) {
+                    renderer.showPreviewFrameSync();
+                    mGLRootView.setVisibility(View.VISIBLE);
+                } else {
+                    if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
+                        renderer.showPreviewFrame();
+                    } else {
+                        renderer.alignFrameSync();
+                        mMosaicFrameProcessor.processFrame();
+                    }
+                }
+            }
+        };
+
+        PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
+        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
+
+        mOrientationEventListener = new PanoOrientationEventListener(mActivity);
+
+        mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();
+
+        Resources appRes = mActivity.getResources();
+        mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview);
+        mDialogTitle = appRes.getString(R.string.pano_dialog_title);
+        mDialogOkString = appRes.getString(R.string.dialog_ok);
+        mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed);
+        mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous);
+
+        mGLRootView = (GLRootView) mActivity.getGLRoot();
+
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mLocationManager = new LocationManager(mActivity, null);
+
+        mMainHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MSG_LOW_RES_FINAL_MOSAIC_READY:
+                        onBackgroundThreadFinished();
+                        showFinalMosaic((Bitmap) msg.obj);
+                        saveHighResMosaic();
+                        break;
+                    case MSG_GENERATE_FINAL_MOSAIC_ERROR:
+                        onBackgroundThreadFinished();
+                        if (mPaused) {
+                            resetToPreview();
+                        } else {
+                            mRotateDialog.showAlertDialog(
+                                    mDialogTitle, mDialogPanoramaFailedString,
+                                    mDialogOkString, new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            resetToPreview();
+                                        }},
+                                    null, null);
+                        }
+                        clearMosaicFrameProcessorIfNeeded();
+                        break;
+                    case MSG_END_DIALOG_RESET_TO_PREVIEW:
+                        onBackgroundThreadFinished();
+                        resetToPreview();
+                        clearMosaicFrameProcessorIfNeeded();
+                        break;
+                    case MSG_CLEAR_SCREEN_DELAY:
+                        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.
+                                FLAG_KEEP_SCREEN_ON);
+                        break;
+                    case MSG_CONFIG_MOSAIC_PREVIEW:
+                        configMosaicPreview(msg.arg1, msg.arg2);
+                        break;
+                    case MSG_RESET_TO_PREVIEW:
+                        resetToPreview();
+                        break;
+                }
+            }
+        };
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        return mActivity.superDispatchTouchEvent(m);
+    }
+
+    private void setupCamera() throws CameraHardwareException, CameraDisabledException {
+        openCamera();
+        Parameters parameters = mCameraDevice.getParameters();
+        setupCaptureParams(parameters);
+        configureCamera(parameters);
+    }
+
+    private void releaseCamera() {
+        if (mCameraDevice != null) {
+            mCameraDevice.setPreviewCallbackWithBuffer(null);
+            CameraHolder.instance().release();
+            mCameraDevice = null;
+            mCameraState = PREVIEW_STOPPED;
+        }
+    }
+
+    private void openCamera() throws CameraHardwareException, CameraDisabledException {
+        int cameraId = CameraHolder.instance().getBackCameraId();
+        // If there is no back camera, use the first camera. Camera id starts
+        // from 0. Currently if a camera is not back facing, it is front facing.
+        // This is also forward compatible if we have a new facing other than
+        // back or front in the future.
+        if (cameraId == -1) cameraId = 0;
+        mCameraDevice = Util.openCamera(mActivity, cameraId);
+        mCameraOrientation = Util.getCameraOrientation(cameraId);
+        if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
+    }
+
+    private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
+            boolean needSmaller) {
+        int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
+        boolean hasFound = false;
+        for (Size size : supportedSizes) {
+            int h = size.height;
+            int w = size.width;
+            // we only want 4:3 format.
+            int d = DEFAULT_CAPTURE_PIXELS - h * w;
+            if (needSmaller && d < 0) { // no bigger preview than 960x720.
+                continue;
+            }
+            if (need4To3 && (h * 4 != w * 3)) {
+                continue;
+            }
+            d = Math.abs(d);
+            if (d < pixelsDiff) {
+                mPreviewWidth = w;
+                mPreviewHeight = h;
+                pixelsDiff = d;
+                hasFound = true;
+            }
+        }
+        return hasFound;
+    }
+
+    private void setupCaptureParams(Parameters parameters) {
+        List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
+        if (!findBestPreviewSize(supportedSizes, true, true)) {
+            Log.w(TAG, "No 4:3 ratio preview size supported.");
+            if (!findBestPreviewSize(supportedSizes, false, true)) {
+                Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
+                findBestPreviewSize(supportedSizes, false, false);
+            }
+        }
+        Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
+        parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
+
+        List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
+        int last = frameRates.size() - 1;
+        int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
+        int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
+        parameters.setPreviewFpsRange(minFps, maxFps);
+        Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
+
+        List<String> supportedFocusModes = parameters.getSupportedFocusModes();
+        if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
+            parameters.setFocusMode(mTargetFocusMode);
+        } else {
+            // Use the default focus mode and log a message
+            Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
+                  " becuase the mode is not supported.");
+        }
+
+        parameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+        mHorizontalViewAngle = parameters.getHorizontalViewAngle();
+        mVerticalViewAngle =  parameters.getVerticalViewAngle();
+    }
+
+    public int getPreviewBufSize() {
+        PixelFormat pixelInfo = new PixelFormat();
+        PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
+        // TODO: remove this extra 32 byte after the driver bug is fixed.
+        return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
+    }
+
+    private void configureCamera(Parameters parameters) {
+        mCameraDevice.setParameters(parameters);
+    }
+
+    private void configMosaicPreview(final int w, final int h) {
+        synchronized (mRendererLock) {
+            if (mIsCreatingRenderer) {
+                mMainHandler.removeMessages(MSG_CONFIG_MOSAIC_PREVIEW);
+                mMainHandler.obtainMessage(MSG_CONFIG_MOSAIC_PREVIEW, w, h).sendToTarget();
+                return;
+            }
+            mIsCreatingRenderer = true;
+        }
+        stopCameraPreview();
+        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+        screenNail.setSize(w, h);
+        synchronized (mRendererLock) {
+            if (mMosaicPreviewRenderer != null) {
+                mMosaicPreviewRenderer.release();
+            }
+            mMosaicPreviewRenderer = null;
+            screenNail.releaseSurfaceTexture();
+            screenNail.acquireSurfaceTexture();
+        }
+        mActivity.notifyScreenNailChanged();
+        final boolean isLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+                SurfaceTexture surfaceTexture = screenNail.getSurfaceTexture();
+                if (surfaceTexture == null) {
+                    synchronized (mRendererLock) {
+                        mIsCreatingRenderer = false;
+                        mRendererLock.notifyAll();
+                        return;
+                    }
+                }
+                MosaicPreviewRenderer renderer = new MosaicPreviewRenderer(
+                        screenNail.getSurfaceTexture(), w, h, isLandscape);
+                synchronized (mRendererLock) {
+                    mMosaicPreviewRenderer = renderer;
+                    mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();
+
+                    if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) {
+                        mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
+                    }
+                    mIsCreatingRenderer = false;
+                    mRendererLock.notifyAll();
+                }
+            }
+        }).start();
+    }
+
+    // Receives the layout change event from the preview area. So we can set
+    // the camera preview screennail to the same size and initialize the mosaic
+    // preview renderer.
+    @Override
+    public void onLayoutChange(View v, int l, int t, int r, int b) {
+        Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t));
+        mActivity.onLayoutChange(v, l, t, r, b);
+        configMosaicPreview(r - l, b - t);
+    }
+
+    @Override
+    public void onFrameAvailable(SurfaceTexture surface) {
+        /* This function may be called by some random thread,
+         * so let's be safe and jump back to ui thread.
+         * No OpenGL calls can be done here. */
+        mActivity.runOnUiThread(mOnFrameAvailableRunnable);
+    }
+
+    private void hideDirectionIndicators() {
+        mLeftIndicator.setVisibility(View.GONE);
+        mRightIndicator.setVisibility(View.GONE);
+    }
+
+    private void showDirectionIndicators(int direction) {
+        switch (direction) {
+            case PanoProgressBar.DIRECTION_NONE:
+                mLeftIndicator.setVisibility(View.VISIBLE);
+                mRightIndicator.setVisibility(View.VISIBLE);
+                break;
+            case PanoProgressBar.DIRECTION_LEFT:
+                mLeftIndicator.setVisibility(View.VISIBLE);
+                mRightIndicator.setVisibility(View.GONE);
+                break;
+            case PanoProgressBar.DIRECTION_RIGHT:
+                mLeftIndicator.setVisibility(View.GONE);
+                mRightIndicator.setVisibility(View.VISIBLE);
+                break;
+        }
+    }
+
+    public void startCapture() {
+        // Reset values so we can do this again.
+        mCancelComputation = false;
+        mTimeTaken = System.currentTimeMillis();
+        mActivity.setSwipingEnabled(false);
+        mActivity.hideSwitcher();
+        mShutterButton.setImageResource(R.drawable.btn_shutter_recording);
+        mCaptureState = CAPTURE_STATE_MOSAIC;
+        mCaptureIndicator.setVisibility(View.VISIBLE);
+        showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
+
+        mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
+            @Override
+            public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
+                    float progressX, float progressY) {
+                float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
+                float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
+                if (isFinished
+                        || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
+                        || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
+                    stopCapture(false);
+                } else {
+                    float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
+                    float panningRateYInDegree = panningRateY * mVerticalViewAngle;
+                    updateProgress(panningRateXInDegree, panningRateYInDegree,
+                            accumulatedHorizontalAngle, accumulatedVerticalAngle);
+                }
+            }
+        });
+
+        mPanoProgressBar.reset();
+        // TODO: calculate the indicator width according to different devices to reflect the actual
+        // angle of view of the camera device.
+        mPanoProgressBar.setIndicatorWidth(20);
+        mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE);
+        mPanoProgressBar.setVisibility(View.VISIBLE);
+        mDeviceOrientationAtCapture = mDeviceOrientation;
+        keepScreenOn();
+        mActivity.getOrientationManager().lockOrientation();
+        setupProgressDirectionMatrix();
+    }
+
+    void setupProgressDirectionMatrix() {
+        int degrees = Util.getDisplayRotation(mActivity);
+        int cameraId = CameraHolder.instance().getBackCameraId();
+        int orientation = Util.getDisplayOrientation(degrees, cameraId);
+        mProgressDirectionMatrix.reset();
+        mProgressDirectionMatrix.postRotate(orientation);
+    }
+
+    private void stopCapture(boolean aborted) {
+        mCaptureState = CAPTURE_STATE_VIEWFINDER;
+        mCaptureIndicator.setVisibility(View.GONE);
+        hideTooFastIndication();
+        hideDirectionIndicators();
+
+        mMosaicFrameProcessor.setProgressListener(null);
+        stopCameraPreview();
+
+        mCameraTexture.setOnFrameAvailableListener(null);
+
+        if (!aborted && !mThreadRunning) {
+            mRotateDialog.showWaitingDialog(mPreparePreviewString);
+            // Hide shutter button, shutter icon, etc when waiting for
+            // panorama to stitch
+            mActivity.hideUI();
+            runBackgroundThread(new Thread() {
+                @Override
+                public void run() {
+                    MosaicJpeg jpeg = generateFinalMosaic(false);
+
+                    if (jpeg != null && jpeg.isValid) {
+                        Bitmap bitmap = null;
+                        bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
+                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
+                                MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
+                    } else {
+                        mMainHandler.sendMessage(mMainHandler.obtainMessage(
+                                MSG_END_DIALOG_RESET_TO_PREVIEW));
+                    }
+                }
+            });
+        }
+        keepScreenOnAwhile();
+    }
+
+    private void showTooFastIndication() {
+        mTooFastPrompt.setVisibility(View.VISIBLE);
+        // The PreviewArea also contains the border for "too fast" indication.
+        mPreviewArea.setVisibility(View.VISIBLE);
+        mPanoProgressBar.setIndicatorColor(mIndicatorColorFast);
+        mLeftIndicator.setEnabled(true);
+        mRightIndicator.setEnabled(true);
+    }
+
+    private void hideTooFastIndication() {
+        mTooFastPrompt.setVisibility(View.GONE);
+        // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout
+        // information so we can know the size and position for mCameraScreenNail.
+        mPreviewArea.setVisibility(View.INVISIBLE);
+        mPanoProgressBar.setIndicatorColor(mIndicatorColor);
+        mLeftIndicator.setEnabled(false);
+        mRightIndicator.setEnabled(false);
+    }
+
+    private void updateProgress(float panningRateXInDegree, float panningRateYInDegree,
+            float progressHorizontalAngle, float progressVerticalAngle) {
+        mGLRootView.requestRender();
+
+        if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD)
+            || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) {
+            showTooFastIndication();
+        } else {
+            hideTooFastIndication();
+        }
+
+        // progressHorizontalAngle and progressVerticalAngle are relative to the
+        // camera. Convert them to UI direction.
+        mProgressAngle[0] = progressHorizontalAngle;
+        mProgressAngle[1] = progressVerticalAngle;
+        mProgressDirectionMatrix.mapPoints(mProgressAngle);
+
+        int angleInMajorDirection =
+                (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))
+                ? (int) mProgressAngle[0]
+                : (int) mProgressAngle[1];
+        mPanoProgressBar.setProgress((angleInMajorDirection));
+    }
+
+    private void setViews(Resources appRes) {
+        mCaptureState = CAPTURE_STATE_VIEWFINDER;
+        mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar);
+        mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
+        mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
+        mPanoProgressBar.setIndicatorColor(mIndicatorColor);
+        mPanoProgressBar.setOnDirectionChangeListener(
+                new PanoProgressBar.OnDirectionChangeListener () {
+                    @Override
+                    public void onDirectionChange(int direction) {
+                        if (mCaptureState == CAPTURE_STATE_MOSAIC) {
+                            showDirectionIndicators(direction);
+                        }
+                    }
+                });
+
+        mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator);
+        mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator);
+        mLeftIndicator.setEnabled(false);
+        mRightIndicator.setEnabled(false);
+        mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview);
+        // This mPreviewArea also shows the border for visual "too fast" indication.
+        mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area);
+        mPreviewArea.setOnLayoutChangeListener(this);
+
+        mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar);
+        mSavingProgressBar.setIndicatorWidth(0);
+        mSavingProgressBar.setMaxProgress(100);
+        mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
+        mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
+
+        mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator);
+
+        mReviewLayout = mRootView.findViewById(R.id.pano_review_layout);
+        mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea);
+        mReview.setBackgroundColor(mReviewBackground);
+        View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button);
+        cancelButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                if (mPaused || mCameraTexture == null) return;
+                cancelHighResComputation();
+            }
+        });
+
+        mShutterButton = mActivity.getShutterButton();
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+        mShutterButton.setOnShutterButtonListener(this);
+
+        if (mActivity.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_PORTRAIT) {
+            Rotatable view = (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea);
+            view.setOrientation(270, false);
+        }
+    }
+
+    private void createContentView() {
+        mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView, true);
+        Resources appRes = mActivity.getResources();
+        mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app);
+        mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
+        mReviewBackground = appRes.getColor(R.color.review_background);
+        mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
+        mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.camera_app_root);
+        mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
+        setViews(appRes);
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        // If mCameraTexture == null then GL setup is not finished yet.
+        // No buttons can be pressed.
+        if (mPaused || mThreadRunning || mCameraTexture == null) return;
+        // Since this button will stay on the screen when capturing, we need to check the state
+        // right now.
+        switch (mCaptureState) {
+            case CAPTURE_STATE_VIEWFINDER:
+                if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return;
+                mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
+                startCapture();
+                break;
+            case CAPTURE_STATE_MOSAIC:
+                mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
+                stopCapture(false);
+        }
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+    }
+
+    public void reportProgress() {
+        mSavingProgressBar.reset();
+        mSavingProgressBar.setRightIncreasing(true);
+        Thread t = new Thread() {
+            @Override
+            public void run() {
+                while (mThreadRunning) {
+                    final int progress = mMosaicFrameProcessor.reportProgress(
+                            true, mCancelComputation);
+
+                    try {
+                        synchronized (mWaitObject) {
+                            mWaitObject.wait(50);
+                        }
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException("Panorama reportProgress failed", e);
+                    }
+                    // Update the progress bar
+                    mActivity.runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            mSavingProgressBar.setProgress(progress);
+                        }
+                    });
+                }
+            }
+        };
+        t.start();
+    }
+
+    private int getCaptureOrientation() {
+        // The panorama image returned from the library is oriented based on the
+        // natural orientation of a camera. We need to set an orientation for the image
+        // in its EXIF header, so the image can be displayed correctly.
+        // The orientation is calculated from compensating the
+        // device orientation at capture and the camera orientation respective to
+        // the natural orientation of the device.
+        int orientation;
+        if (mUsingFrontCamera) {
+            // mCameraOrientation is negative with respect to the front facing camera.
+            // See document of android.hardware.Camera.Parameters.setRotation.
+            orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360;
+        } else {
+            orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
+        }
+        return orientation;
+    }
+
+    public void saveHighResMosaic() {
+        runBackgroundThread(new Thread() {
+            @Override
+            public void run() {
+                mPartialWakeLock.acquire();
+                MosaicJpeg jpeg;
+                try {
+                    jpeg = generateFinalMosaic(true);
+                } finally {
+                    mPartialWakeLock.release();
+                }
+
+                if (jpeg == null) {  // Cancelled by user.
+                    mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW);
+                } else if (!jpeg.isValid) {  // Error when generating mosaic.
+                    mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
+                } else {
+                    int orientation = getCaptureOrientation();
+                    Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
+                    if (uri != null) {
+                        mActivity.addSecureAlbumItemIfNeeded(false, uri);
+                        Util.broadcastNewPicture(mActivity, uri);
+                    }
+                    mMainHandler.sendMessage(
+                            mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW));
+                }
+            }
+        });
+        reportProgress();
+    }
+
+    private void runBackgroundThread(Thread thread) {
+        mThreadRunning = true;
+        thread.start();
+    }
+
+    private void onBackgroundThreadFinished() {
+        mThreadRunning = false;
+        mRotateDialog.dismissDialog();
+    }
+
+    private void cancelHighResComputation() {
+        mCancelComputation = true;
+        synchronized (mWaitObject) {
+            mWaitObject.notify();
+        }
+    }
+
+    // This function will be called upon the first camera frame is available.
+    private void reset() {
+        mCaptureState = CAPTURE_STATE_VIEWFINDER;
+
+        mActivity.getOrientationManager().unlockOrientation();
+        // We should set mGLRootView visible too. However, since there might be no
+        // frame available yet, setting mGLRootView visible should be done right after
+        // the first camera frame is available and therefore it is done by
+        // mOnFirstFrameAvailableRunnable.
+        mActivity.setSwipingEnabled(true);
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+        mReviewLayout.setVisibility(View.GONE);
+        mPanoProgressBar.setVisibility(View.GONE);
+        mGLRootView.setVisibility(View.VISIBLE);
+        // Orientation change will trigger onLayoutChange->configMosaicPreview->
+        // resetToPreview. Do not show the capture UI in film strip.
+        if (mActivity.mShowCameraAppView) {
+            mCaptureLayout.setVisibility(View.VISIBLE);
+            mActivity.showUI();
+        }
+        mMosaicFrameProcessor.reset();
+    }
+
+    private void resetToPreview() {
+        reset();
+        if (!mPaused) startCameraPreview();
+    }
+
+    private static class FlipBitmapDrawable extends BitmapDrawable {
+
+        public FlipBitmapDrawable(Resources res, Bitmap bitmap) {
+            super(res, bitmap);
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            Rect bounds = getBounds();
+            int cx = bounds.centerX();
+            int cy = bounds.centerY();
+            canvas.save(Canvas.MATRIX_SAVE_FLAG);
+            canvas.rotate(180, cx, cy);
+            super.draw(canvas);
+            canvas.restore();
+        }
+    }
+
+    private void showFinalMosaic(Bitmap bitmap) {
+        if (bitmap != null) {
+            int orientation = getCaptureOrientation();
+            if (orientation >= 180) {
+                // We need to flip the drawable to compensate
+                mReview.setImageDrawable(new FlipBitmapDrawable(
+                        mActivity.getResources(), bitmap));
+            } else {
+                mReview.setImageBitmap(bitmap);
+            }
+        }
+
+        mCaptureLayout.setVisibility(View.GONE);
+        mReviewLayout.setVisibility(View.VISIBLE);
+    }
+
+    private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
+        if (jpegData != null) {
+            String filename = PanoUtil.createName(
+                    mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
+            String filepath = Storage.generateFilepath(filename);
+
+            Location loc = mLocationManager.getCurrentLocation();
+            ExifInterface exif = new ExifInterface();
+            try {
+                exif.readExif(jpegData);
+                exif.addGpsDateTimeStampTag(mTimeTaken);
+                exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken,
+                        TimeZone.getDefault());
+                exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+                        ExifInterface.getOrientationValueForRotation(orientation)));
+                writeLocation(loc, exif);
+                exif.writeExif(jpegData, filepath);
+            } catch (IOException e) {
+                Log.e(TAG, "Cannot set exif for " + filepath, e);
+                Storage.writeFile(filepath, jpegData);
+            }
+            int jpegLength = (int) (new File(filepath).length());
+            return Storage.addImage(mContentResolver, filename, mTimeTaken,
+                    loc, orientation, jpegLength, filepath, width, height);
+        }
+        return null;
+    }
+
+    private static void writeLocation(Location location, ExifInterface exif) {
+        if (location == null) {
+            return;
+        }
+        exif.addGpsTags(location.getLatitude(), location.getLongitude());
+        exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider()));
+    }
+
+    private void clearMosaicFrameProcessorIfNeeded() {
+        if (!mPaused || mThreadRunning) return;
+        // Only clear the processor if it is initialized by this activity
+        // instance. Other activity instances may be using it.
+        if (mMosaicFrameProcessorInitialized) {
+            mMosaicFrameProcessor.clear();
+            mMosaicFrameProcessorInitialized = false;
+        }
+    }
+
+    private void initMosaicFrameProcessorIfNeeded() {
+        if (mPaused || mThreadRunning) return;
+        mMosaicFrameProcessor.initialize(
+                mPreviewWidth, mPreviewHeight, getPreviewBufSize());
+        mMosaicFrameProcessorInitialized = true;
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+        mOrientationEventListener.disable();
+        if (mCameraDevice == null) {
+            // Camera open failed. Nothing should be done here.
+            return;
+        }
+        // Stop the capturing first.
+        if (mCaptureState == CAPTURE_STATE_MOSAIC) {
+            stopCapture(true);
+            reset();
+        }
+
+        releaseCamera();
+        synchronized (mRendererLock) {
+            mCameraTexture = null;
+
+            // The preview renderer might not have a chance to be initialized
+            // before onPause().
+            if (mMosaicPreviewRenderer != null) {
+                mMosaicPreviewRenderer.release();
+                mMosaicPreviewRenderer = null;
+            }
+        }
+
+        clearMosaicFrameProcessorIfNeeded();
+        if (mWaitProcessorTask != null) {
+            mWaitProcessorTask.cancel(true);
+            mWaitProcessorTask = null;
+        }
+        resetScreenOn();
+        if (mSoundPlayer != null) {
+            mSoundPlayer.release();
+            mSoundPlayer = null;
+        }
+        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+        screenNail.releaseSurfaceTexture();
+        System.gc();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Drawable lowResReview = null;
+        if (mThreadRunning) lowResReview = mReview.getDrawable();
+
+        // Change layout in response to configuration change
+        mCaptureLayout.setOrientation(
+                newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
+                ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
+        mCaptureLayout.removeAllViews();
+        LayoutInflater inflater = mActivity.getLayoutInflater();
+        inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout);
+
+        mPanoLayout.removeView(mReviewLayout);
+        inflater.inflate(R.layout.pano_review, mPanoLayout);
+
+        setViews(mActivity.getResources());
+        if (mThreadRunning) {
+            mReview.setImageDrawable(lowResReview);
+            mCaptureLayout.setVisibility(View.GONE);
+            mReviewLayout.setVisibility(View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        mOrientationEventListener.enable();
+
+        mCaptureState = CAPTURE_STATE_VIEWFINDER;
+
+        try {
+            setupCamera();
+        } catch (CameraHardwareException e) {
+            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+            return;
+        } catch (CameraDisabledException e) {
+            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+            return;
+        }
+
+        // Set up sound playback for shutter button
+        mSoundPlayer = SoundClips.getPlayer(mActivity);
+
+        // Check if another panorama instance is using the mosaic frame processor.
+        mRotateDialog.dismissDialog();
+        if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
+            mGLRootView.setVisibility(View.GONE);
+            mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString);
+            // If stitching is still going on, make sure switcher and shutter button
+            // are not showing
+            mActivity.hideUI();
+            mWaitProcessorTask = new WaitProcessorTask().execute();
+        } else {
+            mGLRootView.setVisibility(View.VISIBLE);
+            // Camera must be initialized before MosaicFrameProcessor is
+            // initialized. The preview size has to be decided by camera device.
+            initMosaicFrameProcessorIfNeeded();
+            int w = mPreviewArea.getWidth();
+            int h = mPreviewArea.getHeight();
+            if (w != 0 && h != 0) {  // The layout has been calculated.
+                configMosaicPreview(w, h);
+            }
+        }
+        keepScreenOnAwhile();
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(mPreferences,
+                mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+        mRootView.requestLayout();
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_CAMERA, "PanoramaModule");
+    }
+
+    /**
+     * Generate the final mosaic image.
+     *
+     * @param highRes flag to indicate whether we want to get a high-res version.
+     * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
+     *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
+     *         is an error in generating the final mosaic.
+     */
+    public MosaicJpeg generateFinalMosaic(boolean highRes) {
+        int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
+        if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
+            return null;
+        } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
+            return new MosaicJpeg();
+        }
+
+        byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
+        if (imageData == null) {
+            Log.e(TAG, "getFinalMosaicNV21() returned null.");
+            return new MosaicJpeg();
+        }
+
+        int len = imageData.length - 8;
+        int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
+                + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
+        int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
+                + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
+        Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
+
+        if (width <= 0 || height <= 0) {
+            // TODO: pop up an error message indicating that the final result is not generated.
+            Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
+                    height);
+            return new MosaicJpeg();
+        }
+
+        YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
+        try {
+            out.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in storing final mosaic", e);
+            return new MosaicJpeg();
+        }
+        return new MosaicJpeg(out.toByteArray(), width, height);
+    }
+
+    private void startCameraPreview() {
+        if (mCameraDevice == null) {
+            // Camera open failed. Return.
+            return;
+        }
+
+        // This works around a driver issue. startPreview may fail if
+        // stopPreview/setPreviewTexture/startPreview are called several times
+        // in a row. mCameraTexture can be null after pressing home during
+        // mosaic generation and coming back. Preview will be started later in
+        // onLayoutChange->configMosaicPreview. This also reduces the latency.
+        synchronized (mRendererLock) {
+            if (mCameraTexture == null) return;
+
+            // If we're previewing already, stop the preview first (this will
+            // blank the screen).
+            if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
+
+            // Set the display orientation to 0, so that the underlying mosaic
+            // library can always get undistorted mPreviewWidth x mPreviewHeight
+            // image data from SurfaceTexture.
+            mCameraDevice.setDisplayOrientation(0);
+
+            mCameraTexture.setOnFrameAvailableListener(this);
+            mCameraDevice.setPreviewTextureAsync(mCameraTexture);
+        }
+        mCameraDevice.startPreviewAsync();
+        mCameraState = PREVIEW_ACTIVE;
+    }
+
+    private void stopCameraPreview() {
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            Log.v(TAG, "stopPreview");
+            mCameraDevice.stopPreview();
+        }
+        mCameraState = PREVIEW_STOPPED;
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        // If panorama is generating low res or high res mosaic, ignore back
+        // key. So the activity will not be destroyed.
+        if (mThreadRunning) return true;
+        return false;
+    }
+
+    private void resetScreenOn() {
+        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    private void keepScreenOn() {
+        mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private class WaitProcessorTask extends AsyncTask<Void, Void, Void> {
+        @Override
+        protected Void doInBackground(Void... params) {
+            synchronized (mMosaicFrameProcessor) {
+                while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
+                    try {
+                        mMosaicFrameProcessor.wait();
+                    } catch (Exception e) {
+                        // ignore
+                    }
+                }
+            }
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            mWaitProcessorTask = null;
+            mRotateDialog.dismissDialog();
+            mGLRootView.setVisibility(View.VISIBLE);
+            initMosaicFrameProcessorIfNeeded();
+            int w = mPreviewArea.getWidth();
+            int h = mPreviewArea.getHeight();
+            if (w != 0 && h != 0) {  // The layout has been calculated.
+                configMosaicPreview(w, h);
+            }
+            resetToPreview();
+        }
+    }
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+    }
+
+
+    @Override
+    public void onStop() {
+    }
+
+    @Override
+    public void installIntentFilter() {
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    }
+
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+    }
+
+    @Override
+    public void onPreviewTextureCopied() {
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return false;
+    }
+
+    @Override
+    public void updateCameraAppView() {
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return true;
+    }
+
+    @Override
+    public boolean needsPieMenu() {
+        return false;
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+    }
+
+    @Override
+    public void onMediaSaveServiceConnected(MediaSaveService s) {
+        // do nothing.
+    }
+}
diff --git a/src/com/android/camera/PhotoController.java b/src/com/android/camera/PhotoController.java
new file mode 100644
index 0000000..b76022e
--- /dev/null
+++ b/src/com/android/camera/PhotoController.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;
+
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import com.android.camera.ShutterButton.OnShutterButtonListener;
+
+
+public interface PhotoController extends OnShutterButtonListener {
+
+    public static final int PREVIEW_STOPPED = 0;
+    public static final int IDLE = 1;  // preview is active
+    // Focus is in progress. The exact focus state is in Focus.java.
+    public static final int FOCUSING = 2;
+    public static final int SNAPSHOT_IN_PROGRESS = 3;
+    // Switching between cameras.
+    public static final int SWITCHING_CAMERA = 4;
+
+    // returns the actual set zoom value
+    public int onZoomChanged(int requestedZoom);
+
+    public boolean isImageCaptureIntent();
+
+    public boolean isCameraIdle();
+
+    public void onCaptureDone();
+
+    public void onCaptureCancelled();
+
+    public void onCaptureRetake();
+
+    public void cancelAutoFocus();
+
+    public void stopPreview();
+
+    public int getCameraState();
+
+    public void onSingleTapUp(View view, int x, int y);
+
+    public void onSurfaceCreated(SurfaceHolder holder);
+
+    public void onCountDownFinished();
+
+    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight);
+
+}
diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java
new file mode 100644
index 0000000..d0f21ed
--- /dev/null
+++ b/src/com/android/camera/PhotoMenu.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 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.res.Resources;
+import android.hardware.Camera.Parameters;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CountdownTimerPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.gallery3d.R;
+
+import java.util.Locale;
+
+public class PhotoMenu extends PieController
+        implements CountdownTimerPopup.Listener,
+        ListPrefSettingPopup.Listener {
+    private static String TAG = "CAM_photomenu";
+
+    private final String mSettingOff;
+
+    private PhotoUI mUI;
+    private AbstractSettingPopup mPopup;
+    private CameraActivity mActivity;
+
+    public PhotoMenu(CameraActivity activity, PhotoUI ui, PieRenderer pie) {
+        super(activity, pie);
+        mUI = ui;
+        mSettingOff = activity.getString(R.string.setting_off_value);
+        mActivity = activity;
+    }
+
+    public void initialize(PreferenceGroup group) {
+        super.initialize(group);
+        mPopup = null;
+        PieItem item = null;
+        final Resources res = mActivity.getResources();
+        Locale locale = res.getConfiguration().locale;
+        // the order is from left to right in the menu
+
+        // hdr
+        if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) {
+            item = makeSwitchItem(CameraSettings.KEY_CAMERA_HDR, true);
+            mRenderer.addItem(item);
+        }
+        // exposure compensation
+        if (group.findPreference(CameraSettings.KEY_EXPOSURE) != null) {
+            item = makeItem(CameraSettings.KEY_EXPOSURE);
+            item.setLabel(res.getString(R.string.pref_exposure_label));
+            mRenderer.addItem(item);
+        }
+        // more settings
+        PieItem more = makeItem(R.drawable.ic_settings_holo_light);
+        more.setLabel(res.getString(R.string.camera_menu_more_label));
+        mRenderer.addItem(more);
+        // flash
+        if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) {
+            item = makeItem(CameraSettings.KEY_FLASH_MODE);
+            item.setLabel(res.getString(R.string.pref_camera_flashmode_label));
+            mRenderer.addItem(item);
+        }
+        // camera switcher
+        if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
+            item = makeSwitchItem(CameraSettings.KEY_CAMERA_ID, false);
+            final PieItem fitem = item;
+            item.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(PieItem item) {
+                    // Find the index of next camera.
+                    ListPreference pref = mPreferenceGroup
+                            .findPreference(CameraSettings.KEY_CAMERA_ID);
+                    if (pref != null) {
+                        int index = pref.findIndexOfValue(pref.getValue());
+                        CharSequence[] values = pref.getEntryValues();
+                        index = (index + 1) % values.length;
+                        pref.setValueIndex(index);
+                        mListener.onCameraPickerClicked(index);
+                    }
+                    updateItem(fitem, CameraSettings.KEY_CAMERA_ID);
+                }
+            });
+            mRenderer.addItem(item);
+        }
+        // location
+        if (group.findPreference(CameraSettings.KEY_RECORD_LOCATION) != null) {
+            item = makeSwitchItem(CameraSettings.KEY_RECORD_LOCATION, true);
+            more.addItem(item);
+            if (mActivity.isSecureCamera()) {
+                // Prevent location preference from getting changed in secure camera mode
+                item.setEnabled(false);
+            }
+        }
+        // countdown timer
+        final ListPreference ctpref = group.findPreference(CameraSettings.KEY_TIMER);
+        final ListPreference beeppref = group.findPreference(CameraSettings.KEY_TIMER_SOUND_EFFECTS);
+        item = makeItem(R.drawable.ic_timer);
+        item.setLabel(res.getString(R.string.pref_camera_timer_title).toUpperCase(locale));
+        item.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(PieItem item) {
+                CountdownTimerPopup timerPopup = (CountdownTimerPopup) mActivity.getLayoutInflater().inflate(
+                        R.layout.countdown_setting_popup, null, false);
+                timerPopup.initialize(ctpref, beeppref);
+                timerPopup.setSettingChangedListener(PhotoMenu.this);
+                mUI.dismissPopup();
+                mPopup = timerPopup;
+                mUI.showPopup(mPopup);
+            }
+        });
+        more.addItem(item);
+        // image size
+        item = makeItem(R.drawable.ic_imagesize);
+        final ListPreference sizePref = group.findPreference(CameraSettings.KEY_PICTURE_SIZE);
+        item.setLabel(res.getString(R.string.pref_camera_picturesize_title).toUpperCase(locale));
+        item.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(PieItem item) {
+                ListPrefSettingPopup popup = (ListPrefSettingPopup) mActivity.getLayoutInflater().inflate(
+                        R.layout.list_pref_setting_popup, null, false);
+                popup.initialize(sizePref);
+                popup.setSettingChangedListener(PhotoMenu.this);
+                mUI.dismissPopup();
+                mPopup = popup;
+                mUI.showPopup(mPopup);
+            }
+        });
+        more.addItem(item);
+        // white balance
+        if (group.findPreference(CameraSettings.KEY_WHITE_BALANCE) != null) {
+            item = makeItem(CameraSettings.KEY_WHITE_BALANCE);
+            item.setLabel(res.getString(R.string.pref_camera_whitebalance_label));
+            more.addItem(item);
+        }
+        // scene mode
+        if (group.findPreference(CameraSettings.KEY_SCENE_MODE) != null) {
+            IconListPreference pref = (IconListPreference) group.findPreference(
+                    CameraSettings.KEY_SCENE_MODE);
+            pref.setUseSingleIcon(true);
+            item = makeItem(CameraSettings.KEY_SCENE_MODE);
+            more.addItem(item);
+        }
+    }
+
+    @Override
+    // Hit when an item in a popup gets selected
+    public void onListPrefChanged(ListPreference pref) {
+        if (mPopup != null) {
+            mUI.dismissPopup();
+        }
+        onSettingChanged(pref);
+    }
+
+    public void popupDismissed() {
+        // the popup gets dismissed
+        if (mPopup != null) {
+            mPopup = null;
+        }
+    }
+
+    // Return true if the preference has the specified key but not the value.
+    private static boolean notSame(ListPreference pref, String key, String value) {
+        return (key.equals(pref.getKey()) && !value.equals(pref.getValue()));
+    }
+
+    private void setPreference(String key, String value) {
+        ListPreference pref = mPreferenceGroup.findPreference(key);
+        if (pref != null && !value.equals(pref.getValue())) {
+            pref.setValue(value);
+            reloadPreferences();
+        }
+    }
+
+    @Override
+    public void onSettingChanged(ListPreference pref) {
+        // Reset the scene mode if HDR is set to on. Reset HDR if scene mode is
+        // set to non-auto.
+        if (notSame(pref, CameraSettings.KEY_CAMERA_HDR, mSettingOff)) {
+            setPreference(CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO);
+        } else if (notSame(pref, CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO)) {
+            setPreference(CameraSettings.KEY_CAMERA_HDR, mSettingOff);
+        }
+        super.onSettingChanged(pref);
+    }
+
+}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
new file mode 100644
index 0000000..e88645d
--- /dev/null
+++ b/src/com/android/camera/PhotoModule.java
@@ -0,0 +1,2103 @@
+/*
+ * Copyright (C) 2012 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.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.media.CameraProfile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+
+public class PhotoModule
+    implements CameraModule,
+    PhotoController,
+    FocusOverlayManager.Listener,
+    CameraPreference.OnPreferenceChangedListener,
+    ShutterButton.OnShutterButtonListener,
+    MediaSaveService.Listener,
+    OnCountDownFinishedListener,
+    SensorEventListener {
+
+    private static final String TAG = "CAM_PhotoModule";
+
+    // We number the request code from 1000 to avoid collision with Gallery.
+    private static final int REQUEST_CROP = 1000;
+
+    private static final int SETUP_PREVIEW = 1;
+    private static final int FIRST_TIME_INIT = 2;
+    private static final int CLEAR_SCREEN_DELAY = 3;
+    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
+    private static final int CHECK_DISPLAY_ROTATION = 5;
+    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
+    private static final int SWITCH_CAMERA = 7;
+    private static final int SWITCH_CAMERA_START_ANIMATION = 8;
+    private static final int CAMERA_OPEN_DONE = 9;
+    private static final int START_PREVIEW_DONE = 10;
+    private static final int OPEN_CAMERA_FAIL = 11;
+    private static final int CAMERA_DISABLED = 12;
+    private static final int CAPTURE_ANIMATION_DONE = 13;
+
+    // The subset of parameters we need to update in setCameraParameters().
+    private static final int UPDATE_PARAM_INITIALIZE = 1;
+    private static final int UPDATE_PARAM_ZOOM = 2;
+    private static final int UPDATE_PARAM_PREFERENCE = 4;
+    private static final int UPDATE_PARAM_ALL = -1;
+
+    // This is the timeout to keep the camera in onPause for the first time
+    // after screen on if the activity is started from secure lock screen.
+    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
+
+    // copied from Camera hierarchy
+    private CameraActivity mActivity;
+    private CameraProxy mCameraDevice;
+    private int mCameraId;
+    private Parameters mParameters;
+    private boolean mPaused;
+
+    private PhotoUI mUI;
+
+    // these are only used by Camera
+
+    // The activity is going to switch to the specified camera id. This is
+    // needed because texture copy is done in GL thread. -1 means camera is not
+    // switching.
+    protected int mPendingSwitchCameraId = -1;
+    private boolean mOpenCameraFail;
+    private boolean mCameraDisabled;
+
+    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
+    // needed to be updated in mUpdateSet.
+    private int mUpdateSet;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private int mZoomValue;  // The current zoom value.
+
+    private Parameters mInitialParams;
+    private boolean mFocusAreaSupported;
+    private boolean mMeteringAreaSupported;
+    private boolean mAeLockSupported;
+    private boolean mAwbLockSupported;
+    private boolean mContinousFocusSupported;
+
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+    private ComboPreferences mPreferences;
+
+    private static final String sTempCropFilename = "crop-temp";
+
+    private ContentProviderClient mMediaProviderClient;
+    private boolean mFaceDetectionStarted = false;
+
+    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
+    private String mCropValue;
+    private Uri mSaveUri;
+
+    // We use a queue to generated names of the images to be used later
+    // when the image is ready to be saved.
+    private NamedImages mNamedImages;
+
+    private Runnable mDoSnapRunnable = new Runnable() {
+        @Override
+        public void run() {
+            onShutterButtonClick();
+        }
+    };
+
+    private Runnable mFlashRunnable = new Runnable() {
+        @Override
+        public void run() {
+            animateFlash();
+        }
+    };
+
+    private final StringBuilder mBuilder = new StringBuilder();
+    private final Formatter mFormatter = new Formatter(mBuilder);
+    private final Object[] mFormatterArgs = new Object[1];
+
+    /**
+     * An unpublished intent flag requesting to return as soon as capturing
+     * is completed.
+     *
+     * TODO: consider publishing by moving into MediaStore.
+     */
+    private static final String EXTRA_QUICK_CAPTURE =
+            "android.intent.extra.quickCapture";
+
+    // The display rotation in degrees. This is only valid when mCameraState is
+    // not PREVIEW_STOPPED.
+    private int mDisplayRotation;
+    // The value for android.hardware.Camera.setDisplayOrientation.
+    private int mCameraDisplayOrientation;
+    // The value for UI components like indicators.
+    private int mDisplayOrientation;
+    // The value for android.hardware.Camera.Parameters.setRotation.
+    private int mJpegRotation;
+    private boolean mFirstTimeInitialized;
+    private boolean mIsImageCaptureIntent;
+
+    private int mCameraState = PREVIEW_STOPPED;
+    private boolean mSnapshotOnIdle = false;
+
+    private ContentResolver mContentResolver;
+
+    private LocationManager mLocationManager;
+
+    private final PostViewPictureCallback mPostViewPictureCallback =
+            new PostViewPictureCallback();
+    private final RawPictureCallback mRawPictureCallback =
+            new RawPictureCallback();
+    private final AutoFocusCallback mAutoFocusCallback =
+            new AutoFocusCallback();
+    private final Object mAutoFocusMoveCallback =
+            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
+            ? new AutoFocusMoveCallback()
+            : null;
+
+    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+    private long mFocusStartTime;
+    private long mShutterCallbackTime;
+    private long mPostViewPictureCallbackTime;
+    private long mRawPictureCallbackTime;
+    private long mJpegPictureCallbackTime;
+    private long mOnResumeTime;
+    private byte[] mJpegImageData;
+
+    // These latency time are for the CameraLatency test.
+    public long mAutoFocusTime;
+    public long mShutterLag;
+    public long mShutterToPictureDisplayedTime;
+    public long mPictureDisplayedToJpegCallbackTime;
+    public long mJpegCallbackFinishTime;
+    public long mCaptureStartTime;
+
+    // This handles everything about focus.
+    private FocusOverlayManager mFocusManager;
+
+    private String mSceneMode;
+
+    private final Handler mHandler = new MainHandler();
+    private PreferenceGroup mPreferenceGroup;
+
+    private boolean mQuickCapture;
+
+    CameraStartUpThread mCameraStartUpThread;
+    ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
+
+    private SensorManager mSensorManager;
+    private float[] mGData = new float[3];
+    private float[] mMData = new float[3];
+    private float[] mR = new float[16];
+    private int mHeading = -1;
+
+    private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+            new MediaSaveService.OnMediaSavedListener() {
+                @Override
+                public void onMediaSaved(Uri uri) {
+                    if (uri != null) {
+                        mActivity.addSecureAlbumItemIfNeeded(false, uri);
+                        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;
+
+        public void cancel() {
+            mCancelled = true;
+            interrupt();
+        }
+
+        public boolean isCanceled() {
+            return mCancelled;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // We need to check whether the activity is paused before long
+                // operations to ensure that onPause() can be done ASAP.
+                if (mCancelled) return;
+                mCameraDevice = Util.openCamera(mActivity, mCameraId);
+                mParameters = mCameraDevice.getParameters();
+                // Wait until all the initialization needed by startPreview are
+                // done.
+                mStartPreviewPrerequisiteReady.block();
+
+                initializeCapabilities();
+                if (mFocusManager == null) initializeFocusManager();
+                if (mCancelled) return;
+                setCameraParameters(UPDATE_PARAM_ALL);
+                mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+                if (mCancelled) return;
+                startPreview();
+                mHandler.sendEmptyMessage(START_PREVIEW_DONE);
+                mOnResumeTime = SystemClock.uptimeMillis();
+                mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
+            } catch (CameraHardwareException e) {
+                mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
+            } catch (CameraDisabledException e) {
+                mHandler.sendEmptyMessage(CAMERA_DISABLED);
+            }
+        }
+    }
+
+    /**
+     * This Handler is used to post message back onto the main thread of the
+     * application
+     */
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SETUP_PREVIEW: {
+                    setupPreview();
+                    break;
+                }
+
+                case CLEAR_SCREEN_DELAY: {
+                    mActivity.getWindow().clearFlags(
+                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+
+                case FIRST_TIME_INIT: {
+                    initializeFirstTime();
+                    break;
+                }
+
+                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
+                    setCameraParametersWhenIdle(0);
+                    break;
+                }
+
+                case CHECK_DISPLAY_ROTATION: {
+                    // Set the display orientation if display rotation has changed.
+                    // Sometimes this happens when the device is held upside
+                    // down and camera app is opened. Rotation animation will
+                    // take some time and the rotation value we have got may be
+                    // wrong. Framework does not have a callback for this now.
+                    if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
+                        setDisplayOrientation();
+                    }
+                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+                    }
+                    break;
+                }
+
+                case SHOW_TAP_TO_FOCUS_TOAST: {
+                    showTapToFocusToast();
+                    break;
+                }
+
+                case SWITCH_CAMERA: {
+                    switchCamera();
+                    break;
+                }
+
+                case SWITCH_CAMERA_START_ANIMATION: {
+                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+                    break;
+                }
+
+                case CAMERA_OPEN_DONE: {
+                    onCameraOpened();
+                    break;
+                }
+
+                case START_PREVIEW_DONE: {
+                    onPreviewStarted();
+                    break;
+                }
+
+                case OPEN_CAMERA_FAIL: {
+                    mCameraStartUpThread = null;
+                    mOpenCameraFail = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.cannot_connect_camera);
+                    break;
+                }
+
+                case CAMERA_DISABLED: {
+                    mCameraStartUpThread = null;
+                    mCameraDisabled = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.camera_disabled);
+                    break;
+                }
+                case CAPTURE_ANIMATION_DONE: {
+                    mUI.enablePreviewThumb(false);
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(CameraActivity activity, View parent, boolean reuseNail) {
+        mActivity = activity;
+        mUI = new PhotoUI(activity, this, parent);
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mCameraId = getPreferredCameraId(mPreferences);
+
+        mContentResolver = mActivity.getContentResolver();
+
+        // To reduce startup time, open the camera and start the preview in
+        // another thread.
+        mCameraStartUpThread = new CameraStartUpThread();
+        mCameraStartUpThread.start();
+
+
+        // Surface texture is from camera screen nail and startPreview needs it.
+        // This must be done before startPreview.
+        mIsImageCaptureIntent = isImageCaptureIntent();
+        if (reuseNail) {
+            mActivity.reuseCameraScreenNail(!mIsImageCaptureIntent);
+        } else {
+            mActivity.createCameraScreenNail(!mIsImageCaptureIntent);
+        }
+
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        // we need to reset exposure for the preview
+        resetExposureCompensation();
+        // Starting the preview needs preferences, camera screen nail, and
+        // focus area indicator.
+        mStartPreviewPrerequisiteReady.open();
+
+        initializeControlByIntent();
+        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+        mLocationManager = new LocationManager(mActivity, mUI);
+        mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));
+
+    }
+
+    private void initializeControlByIntent() {
+        mUI.initializeControlByIntent();
+        if (mIsImageCaptureIntent) {
+            setupCaptureParams();
+        }
+    }
+
+    private void onPreviewStarted() {
+        mCameraStartUpThread = null;
+        setCameraState(IDLE);
+        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
+            // This may happen if surfaceCreated has arrived.
+            mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
+        }
+        startFaceDetection();
+        locationFirstRun();
+    }
+
+    // Prompt the user to pick to record location for the very first run of
+    // camera only
+    private void locationFirstRun() {
+        if (RecordLocationPreference.isSet(mPreferences)) {
+            return;
+        }
+        if (mActivity.isSecureCamera()) return;
+        // Check if the back camera exists
+        int backCameraId = CameraHolder.instance().getBackCameraId();
+        if (backCameraId == -1) {
+            // If there is no back camera, do not show the prompt.
+            return;
+        }
+
+        new AlertDialog.Builder(mActivity)
+            .setTitle(R.string.remember_location_title)
+            .setMessage(R.string.remember_location_prompt)
+            .setPositiveButton(R.string.remember_location_yes, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    setLocationPreference(RecordLocationPreference.VALUE_ON);
+                }
+            })
+            .setNegativeButton(R.string.remember_location_no, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    dialog.cancel();
+                }
+            })
+            .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                @Override
+                public void onCancel(DialogInterface dialog) {
+                    setLocationPreference(RecordLocationPreference.VALUE_OFF);
+                }
+            })
+            .show();
+    }
+
+    private void setLocationPreference(String value) {
+        mPreferences.edit()
+            .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+            .apply();
+        // TODO: Fix this to use the actual onSharedPreferencesChanged listener
+        // instead of invoking manually
+        onSharedPreferenceChanged();
+    }
+
+    private void onCameraOpened() {
+        View root = mUI.getRootView();
+        // These depend on camera parameters.
+
+        int width = root.getWidth();
+        int height = root.getHeight();
+        mFocusManager.setPreviewSize(width, height);
+        // Full-screen screennail
+        if (Util.getDisplayRotation(mActivity) % 180 == 0) {
+            ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(width, height);
+        } else {
+            ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(height, width);
+        }
+        // Set touch focus listener.
+        mActivity.setSingleTapUpListener(root);
+        openCameraCommon();
+        onFullScreenChanged(mActivity.isInCameraApp());
+    }
+
+    private void switchCamera() {
+        if (mPaused) return;
+
+        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
+        mCameraId = mPendingSwitchCameraId;
+        mPendingSwitchCameraId = -1;
+        setCameraId(mCameraId);
+
+        // from onPause
+        closeCamera();
+        mUI.collapseCameraControls();
+        mUI.clearFaces();
+        if (mFocusManager != null) mFocusManager.removeMessages();
+
+        // Restart the camera and initialize the UI. From onCreate.
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        try {
+            mCameraDevice = Util.openCamera(mActivity, mCameraId);
+            mParameters = mCameraDevice.getParameters();
+        } catch (CameraHardwareException e) {
+            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+            return;
+        } catch (CameraDisabledException e) {
+            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+            return;
+        }
+        initializeCapabilities();
+        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+        mFocusManager.setMirror(mirror);
+        mFocusManager.setParameters(mInitialParams);
+        setupPreview();
+
+        openCameraCommon();
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Start switch camera animation. Post a message because
+            // onFrameAvailable from the old camera may already exist.
+            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+        }
+    }
+
+    protected void setCameraId(int cameraId) {
+        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+        pref.setValue("" + cameraId);
+    }
+
+    // either open a new camera or switch cameras
+    private void openCameraCommon() {
+        loadCameraPreferences();
+
+        mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);
+        updateSceneMode();
+        showTapToFocusToastIfNeeded();
+
+
+    }
+
+    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+        Log.d(TAG, "Preview size changed.");
+        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+        ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(
+                previewWidth, previewHeight);
+        mActivity.notifyScreenNailChanged();
+    }
+
+    private void resetExposureCompensation() {
+        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
+                CameraSettings.EXPOSURE_DEFAULT_VALUE);
+        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
+            Editor editor = mPreferences.edit();
+            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
+            editor.apply();
+        }
+    }
+
+    private void keepMediaProviderInstance() {
+        // We want to keep a reference to MediaProvider in camera's lifecycle.
+        // TODO: Utilize mMediaProviderClient instance to replace
+        // ContentResolver calls.
+        if (mMediaProviderClient == null) {
+            mMediaProviderClient = mContentResolver
+                    .acquireContentProviderClient(MediaStore.AUTHORITY);
+        }
+    }
+
+    // Snapshots can only be taken after this is called. It should be called
+    // once only. We could have done these things in onCreate() but we want to
+    // make preview screen appear as soon as possible.
+    private void initializeFirstTime() {
+        if (mFirstTimeInitialized) return;
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        keepMediaProviderInstance();
+
+        mUI.initializeFirstTime();
+        MediaSaveService s = mActivity.getMediaSaveService();
+        // We set the listener only when both service and shutterbutton
+        // are initialized.
+        if (s != null) {
+            s.setListener(this);
+        }
+
+        mNamedImages = new NamedImages();
+
+        mFirstTimeInitialized = true;
+        addIdleHandler();
+
+        mActivity.updateStorageSpaceAndHint();
+    }
+
+    // If the activity is paused and resumed, this method will be called in
+    // onResume.
+    private void initializeSecondTime() {
+        // Start location update if needed.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (s != null) {
+            s.setListener(this);
+        }
+        mNamedImages = new NamedImages();
+        mUI.initializeSecondTime(mParameters);
+        keepMediaProviderInstance();
+    }
+
+    @Override
+    public void onSurfaceCreated(SurfaceHolder holder) {
+        // Do not access the camera if camera start up thread is not finished.
+        if (mCameraDevice == null || mCameraStartUpThread != null)
+            return;
+
+        mCameraDevice.setPreviewDisplayAsync(holder);
+        // This happens when onConfigurationChanged arrives, surface has been
+        // destroyed, and there is no onFullScreenChanged.
+        if (mCameraState == PREVIEW_STOPPED) {
+            setupPreview();
+        }
+    }
+
+    private void showTapToFocusToastIfNeeded() {
+        // Show the tap to focus toast if this is the first start.
+        if (mFocusAreaSupported &&
+                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
+            // Delay the toast for one second to wait for orientation.
+            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
+        }
+    }
+
+    private void addIdleHandler() {
+        MessageQueue queue = Looper.myQueue();
+        queue.addIdleHandler(new MessageQueue.IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                Storage.ensureOSXCompatible();
+                return false;
+            }
+        });
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void startFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = true;
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            mUI.onStartFaceDetection(mDisplayOrientation,
+                    (info.facing == CameraInfo.CAMERA_FACING_FRONT));
+            mCameraDevice.setFaceDetectionListener(mUI);
+            mCameraDevice.startFaceDetection();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void stopFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (!mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = false;
+            mCameraDevice.setFaceDetectionListener(null);
+            mCameraDevice.stopFaceDetection();
+            mUI.clearFaces();
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mCameraState == SWITCHING_CAMERA) return true;
+        return mUI.dispatchTouchEvent(m);
+    }
+
+    private final class ShutterCallback
+            implements android.hardware.Camera.ShutterCallback {
+
+        private boolean mAnimateFlash;
+
+        public ShutterCallback(boolean animateFlash) {
+            mAnimateFlash = animateFlash;
+        }
+
+        @Override
+        public void onShutter() {
+            mShutterCallbackTime = System.currentTimeMillis();
+            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
+            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
+            if (mAnimateFlash) {
+                mActivity.runOnUiThread(mFlashRunnable);
+            }
+        }
+    }
+
+    private final class PostViewPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] data, android.hardware.Camera camera) {
+            mPostViewPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToPostViewCallbackTime = "
+                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
+                    + "ms");
+        }
+    }
+
+    private final class RawPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] rawData, android.hardware.Camera camera) {
+            mRawPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToRawCallbackTime = "
+                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
+        }
+    }
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        @Override
+        public void onPictureTaken(
+                final byte [] jpegData, final android.hardware.Camera camera) {
+            if (mPaused) {
+                return;
+            }
+            if (mSceneMode == Util.SCENE_MODE_HDR) {
+                mActivity.showSwitcher();
+                mActivity.setSwipingEnabled(true);
+            }
+
+            mJpegPictureCallbackTime = System.currentTimeMillis();
+            // If postview callback has arrived, the captured image is displayed
+            // in postview callback. If not, the captured image is displayed in
+            // raw picture callback.
+            if (mPostViewPictureCallbackTime != 0) {
+                mShutterToPictureDisplayedTime =
+                        mPostViewPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
+            } else {
+                mShutterToPictureDisplayedTime =
+                        mRawPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
+            }
+            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+                    + mPictureDisplayedToJpegCallbackTime + "ms");
+
+            // Only animate when in full screen capture mode
+            // i.e. If monkey/a user swipes to the gallery during picture taking,
+            // don't show animation
+            if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                    && mActivity.mShowCameraAppView) {
+                // Finish capture animation
+                mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
+                ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
+                mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+                        CaptureAnimManager.getAnimationDuration());
+            }
+            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
+            if (!mIsImageCaptureIntent) {
+                if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
+                    setupPreview();
+                } else {
+                    // Camera HAL of some devices have a bug. Starting preview
+                    // immediately after taking a picture will fail. Wait some
+                    // time before starting the preview.
+                    mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
+                }
+            }
+
+            if (!mIsImageCaptureIntent) {
+                // Calculate the width and the height of the jpeg.
+                Size s = mParameters.getPictureSize();
+                ExifInterface exif = Exif.getExif(jpegData);
+                int orientation = Exif.getOrientation(exif);
+                int width, height;
+                if ((mJpegRotation + orientation) % 180 == 0) {
+                    width = s.width;
+                    height = s.height;
+                } else {
+                    width = s.height;
+                    height = s.width;
+                }
+                String title = mNamedImages.getTitle();
+                long date = mNamedImages.getDate();
+                if (title == null) {
+                    Log.e(TAG, "Unbalanced name/data pair");
+                } else {
+                    if (date == -1) date = mCaptureStartTime;
+                    if (mHeading >= 0) {
+                        // heading direction has been updated by the sensor.
+                        ExifTag directionRefTag = exif.buildTag(
+                                ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+                                ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
+                        ExifTag directionTag = exif.buildTag(
+                                ExifInterface.TAG_GPS_IMG_DIRECTION,
+                                new Rational(mHeading, 1));
+                        exif.setTag(directionRefTag);
+                        exif.setTag(directionTag);
+                    }
+                    mActivity.getMediaSaveService().addImage(
+                            jpegData, title, date, mLocation, width, height,
+                            orientation, exif, mOnMediaSavedListener, mContentResolver);
+                }
+            } else {
+                mJpegImageData = jpegData;
+                if (!mQuickCapture) {
+                    mUI.showPostCaptureAlert();
+                } else {
+                    onCaptureDone();
+                }
+            }
+
+            // Check this in advance of each shot so we don't add to shutter
+            // latency. It's true that someone else could write to the SD card in
+            // the mean time and fill it, but that could have happened between the
+            // shutter press and saving the JPEG too.
+            mActivity.updateStorageSpaceAndHint();
+
+            long now = System.currentTimeMillis();
+            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
+            Log.v(TAG, "mJpegCallbackFinishTime = "
+                    + mJpegCallbackFinishTime + "ms");
+            mJpegPictureCallbackTime = 0;
+        }
+    }
+
+    private final class AutoFocusCallback
+            implements android.hardware.Camera.AutoFocusCallback {
+        @Override
+        public void onAutoFocus(
+                boolean focused, android.hardware.Camera camera) {
+            if (mPaused) return;
+
+            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
+            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
+            setCameraState(IDLE);
+            mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private final class AutoFocusMoveCallback
+            implements android.hardware.Camera.AutoFocusMoveCallback {
+        @Override
+        public void onAutoFocusMoving(
+            boolean moving, android.hardware.Camera camera) {
+                mFocusManager.onAutoFocusMoving(moving);
+        }
+    }
+
+    private static class NamedImages {
+        private ArrayList<NamedEntity> mQueue;
+        private boolean mStop;
+        private NamedEntity mNamedEntity;
+
+        public NamedImages() {
+            mQueue = new ArrayList<NamedEntity>();
+        }
+
+        public void nameNewImage(ContentResolver resolver, long date) {
+            NamedEntity r = new NamedEntity();
+            r.title = Util.createJpegName(date);
+            r.date = date;
+            mQueue.add(r);
+        }
+
+        public String getTitle() {
+            if (mQueue.isEmpty()) {
+                mNamedEntity = null;
+                return null;
+            }
+            mNamedEntity = mQueue.get(0);
+            mQueue.remove(0);
+
+            return mNamedEntity.title;
+        }
+
+        // Must be called after getTitle().
+        public long getDate() {
+            if (mNamedEntity == null) return -1;
+            return mNamedEntity.date;
+        }
+
+        private static class NamedEntity {
+            String title;
+            long date;
+        }
+    }
+
+    private void setCameraState(int state) {
+        mCameraState = state;
+        switch (state) {
+        case PhotoController.PREVIEW_STOPPED:
+        case PhotoController.SNAPSHOT_IN_PROGRESS:
+        case PhotoController.FOCUSING:
+        case PhotoController.SWITCHING_CAMERA:
+            mUI.enableGestures(false);
+            break;
+        case PhotoController.IDLE:
+            if (mActivity.isInCameraApp()) {
+                mUI.enableGestures(true);
+            }
+            break;
+        }
+    }
+
+    private void animateFlash() {
+        // Only animate when in full screen capture mode
+        // i.e. If monkey/a user swipes to the gallery during picture taking,
+        // don't show animation
+        if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                && mActivity.mShowCameraAppView) {
+            // Start capture animation.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).animateFlash(mDisplayRotation);
+            mUI.enablePreviewThumb(true);
+            mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+                    CaptureAnimManager.getAnimationDuration());
+        }
+    }
+
+    @Override
+    public boolean capture() {
+        // 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
+                || mActivity.getMediaSaveService().isQueueFull()) {
+            return false;
+        }
+        mCaptureStartTime = System.currentTimeMillis();
+        mPostViewPictureCallbackTime = 0;
+        mJpegImageData = null;
+
+        final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
+
+        if (animateBefore) {
+            animateFlash();
+        }
+
+        // Set rotation and gps data.
+        int orientation;
+        // We need to be consistent with the framework orientation (i.e. the
+        // orientation of the UI.) when the auto-rotate screen setting is on.
+        if (mActivity.isAutoRotateScreen()) {
+            orientation = (360 - mDisplayRotation) % 360;
+        } else {
+            orientation = mOrientation;
+        }
+        mJpegRotation = Util.getJpegRotation(mCameraId, orientation);
+        mParameters.setRotation(mJpegRotation);
+        Location loc = mLocationManager.getCurrentLocation();
+        Util.setGpsParameters(mParameters, loc);
+        mCameraDevice.setParameters(mParameters);
+
+        mCameraDevice.takePicture2(new ShutterCallback(!animateBefore),
+                mRawPictureCallback, mPostViewPictureCallback,
+                new JpegPictureCallback(loc), mCameraState,
+                mFocusManager.getFocusState());
+
+        mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime);
+
+        mFaceDetectionStarted = false;
+        setCameraState(SNAPSHOT_IN_PROGRESS);
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_DONE, "Photo");
+        return true;
+    }
+
+    @Override
+    public void setFocusParameters() {
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    private int getPreferredCameraId(ComboPreferences preferences) {
+        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+        if (intentCameraId != -1) {
+            // Testing purpose. Launch a specific camera through the intent
+            // extras.
+            return intentCameraId;
+        } else {
+            return CameraSettings.readPreferredCameraId(preferences);
+        }
+    }
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+        mUI.onFullScreenChanged(full);
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            if (mActivity.mCameraScreenNail != null) {
+                ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
+            }
+            return;
+        }
+    }
+
+    private void updateSceneMode() {
+        // If scene mode is set, we cannot set flash mode, white balance, and
+        // focus mode, instead, we read it from driver
+        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            overrideCameraSettings(mParameters.getFlashMode(),
+                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
+        } else {
+            overrideCameraSettings(null, null, null);
+        }
+    }
+
+    private void overrideCameraSettings(final String flashMode,
+            final String whiteBalance, final String focusMode) {
+        mUI.overrideSettings(
+                CameraSettings.KEY_FLASH_MODE, flashMode,
+                CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+                CameraSettings.KEY_FOCUS_MODE, focusMode);
+    }
+
+    private void loadCameraPreferences() {
+        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
+                mCameraId, CameraHolder.instance().getCameraInfo());
+        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
+    }
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+        // We keep the last known orientation. So if the user first orient
+        // the camera then point the camera to floor or sky, we still have
+        // the correct orientation.
+        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+        mOrientation = Util.roundOrientation(orientation, mOrientation);
+
+        // Show the toast after getting the first orientation changed.
+        if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
+            mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
+            showTapToFocusToast();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mMediaProviderClient != null) {
+            mMediaProviderClient.release();
+            mMediaProviderClient = null;
+        }
+    }
+
+    @Override
+    public void onCaptureCancelled() {
+        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
+        mActivity.finish();
+    }
+
+    @Override
+    public void onCaptureRetake() {
+        if (mPaused)
+            return;
+        mUI.hidePostCaptureAlert();
+        setupPreview();
+    }
+
+    @Override
+    public void onCaptureDone() {
+        if (mPaused) {
+            return;
+        }
+
+        byte[] data = mJpegImageData;
+
+        if (mCropValue == null) {
+            // First handle the no crop case -- just return the value.  If the
+            // caller specifies a "save uri" then write the data to its
+            // stream. Otherwise, pass back a scaled down version of the bitmap
+            // directly in the extras.
+            if (mSaveUri != null) {
+                OutputStream outputStream = null;
+                try {
+                    outputStream = mContentResolver.openOutputStream(mSaveUri);
+                    outputStream.write(data);
+                    outputStream.close();
+
+                    mActivity.setResultEx(Activity.RESULT_OK);
+                    mActivity.finish();
+                } catch (IOException ex) {
+                    // ignore exception
+                } finally {
+                    Util.closeSilently(outputStream);
+                }
+            } else {
+                ExifInterface exif = Exif.getExif(data);
+                int orientation = Exif.getOrientation(exif);
+                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
+                bitmap = Util.rotate(bitmap, orientation);
+                mActivity.setResultEx(Activity.RESULT_OK,
+                        new Intent("inline-data").putExtra("data", bitmap));
+                mActivity.finish();
+            }
+        } else {
+            // Save the image to a temp file and invoke the cropper
+            Uri tempUri = null;
+            FileOutputStream tempStream = null;
+            try {
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
+                tempStream.write(data);
+                tempStream.close();
+                tempUri = Uri.fromFile(path);
+            } catch (FileNotFoundException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } catch (IOException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } finally {
+                Util.closeSilently(tempStream);
+            }
+
+            Bundle newExtras = new Bundle();
+            if (mCropValue.equals("circle")) {
+                newExtras.putString("circleCrop", "true");
+            }
+            if (mSaveUri != null) {
+                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
+            } else {
+                newExtras.putBoolean(CropExtras.KEY_RETURN_DATA, true);
+            }
+            if (mActivity.isSecureCamera()) {
+                newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
+            }
+
+            Intent cropIntent = new Intent(FilterShowActivity.CROP_ACTION);
+
+            cropIntent.setData(tempUri);
+            cropIntent.putExtras(newExtras);
+
+            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
+        }
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+        if (mPaused || mUI.collapseCameraControls()
+                || (mCameraState == SNAPSHOT_IN_PROGRESS)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not do focus if there is not enough storage.
+        if (pressed && !canTakePicture()) return;
+
+        if (pressed) {
+            mFocusManager.onShutterDown();
+        } else {
+            // for countdown mode, we need to postpone the shutter release
+            // i.e. lock the focus during countdown.
+            if (!mUI.isCountingDown()) {
+                mFocusManager.onShutterUp();
+            }
+        }
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        if (mPaused || mUI.collapseCameraControls()
+                || (mCameraState == SWITCHING_CAMERA)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not take the picture if there is not enough storage.
+        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+            Log.i(TAG, "Not enough space or storage not ready. remaining="
+                    + mActivity.getStorageSpace());
+            return;
+        }
+        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
+
+        if (mSceneMode == Util.SCENE_MODE_HDR) {
+            mActivity.hideSwitcher();
+            mActivity.setSwipingEnabled(false);
+        }
+        // If the user wants to do a snapshot while the previous one is still
+        // in progress, remember the fact and do it after we finish the previous
+        // one and re-start the preview. Snapshot in progress also includes the
+        // state that autofocus is focusing and a picture will be taken when
+        // focus callback arrives.
+        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
+                && !mIsImageCaptureIntent) {
+            mSnapshotOnIdle = true;
+            return;
+        }
+
+        String timer = mPreferences.getString(
+                CameraSettings.KEY_TIMER,
+                mActivity.getString(R.string.pref_camera_timer_default));
+        boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+                mActivity.getString(R.string.pref_camera_timer_sound_default))
+                .equals(mActivity.getString(R.string.setting_on_value));
+
+        int seconds = Integer.parseInt(timer);
+        // When shutter button is pressed, check whether the previous countdown is
+        // finished. If not, cancel the previous countdown and start a new one.
+        if (mUI.isCountingDown()) {
+            mUI.cancelCountDown();
+        }
+        if (seconds > 0) {
+            mUI.startCountDown(seconds, playSound);
+        } else {
+           mSnapshotOnIdle = false;
+           mFocusManager.doSnap();
+        }
+    }
+
+    @Override
+    public void installIntentFilter() {
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return mFirstTimeInitialized;
+    }
+
+    @Override
+    public void updateCameraAppView() {
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        if (mOpenCameraFail || mCameraDisabled) return;
+
+        mJpegPictureCallbackTime = 0;
+        mZoomValue = 0;
+
+        // Start the preview if it is not started.
+        if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
+            resetExposureCompensation();
+            mCameraStartUpThread = new CameraStartUpThread();
+            mCameraStartUpThread.start();
+        }
+
+        // If first time initialization is not finished, put it in the
+        // message queue.
+        if (!mFirstTimeInitialized) {
+            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
+        } else {
+            initializeSecondTime();
+        }
+        keepScreenOnAwhile();
+
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
+
+        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (gsensor != null) {
+            mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+
+        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        if (msensor != null) {
+            mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+    }
+
+    void waitCameraStartUpThread() {
+        try {
+            if (mCameraStartUpThread != null) {
+                mCameraStartUpThread.cancel();
+                mCameraStartUpThread.join();
+                mCameraStartUpThread = null;
+                setCameraState(IDLE);
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (gsensor != null) {
+            mSensorManager.unregisterListener(this, gsensor);
+        }
+
+        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        if (msensor != null) {
+            mSensorManager.unregisterListener(this, msensor);
+        }
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+        // Wait the camera start up thread to finish.
+        waitCameraStartUpThread();
+
+        // When camera is started from secure lock screen for the first time
+        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
+        // To reduce the latency, keep the camera for a short time so it does
+        // not need to be opened again.
+        if (mCameraDevice != null && mActivity.isSecureCamera()
+                && ActivityBase.isFirstStartAfterScreenOn()) {
+            ActivityBase.resetFirstStartAfterScreenOn();
+            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
+        }
+        // Reset the focus first. Camera CTS does not guarantee that
+        // cancelAutoFocus is allowed after preview stops.
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            mCameraDevice.cancelAutoFocus();
+        }
+        stopPreview();
+        // Release surface texture.
+        ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture();
+
+        mNamedImages = null;
+
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+        // If we are in an image capture intent and has taken
+        // a picture, we just clear it in onPause.
+        mJpegImageData = null;
+
+        // Remove the messages in the event queue.
+        mHandler.removeMessages(SETUP_PREVIEW);
+        mHandler.removeMessages(FIRST_TIME_INIT);
+        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+        mHandler.removeMessages(SWITCH_CAMERA);
+        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+        mHandler.removeMessages(CAMERA_OPEN_DONE);
+        mHandler.removeMessages(START_PREVIEW_DONE);
+        mHandler.removeMessages(OPEN_CAMERA_FAIL);
+        mHandler.removeMessages(CAMERA_DISABLED);
+
+        closeCamera();
+
+        resetScreenOn();
+        mUI.onPause();
+
+        mPendingSwitchCameraId = -1;
+        if (mFocusManager != null) mFocusManager.removeMessages();
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (s != null) {
+            s.setListener(null);
+        }
+    }
+
+    /**
+     * The focus manager is the first UI related element to get initialized,
+     * and it requires the RenderOverlay, so initialize it here
+     */
+    private void initializeFocusManager() {
+        // Create FocusManager object. startPreview needs it.
+        // if mFocusManager not null, reuse it
+        // otherwise create a new instance
+        if (mFocusManager != null) {
+            mFocusManager.removeMessages();
+        } else {
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+            String[] defaultFocusModes = mActivity.getResources().getStringArray(
+                    R.array.pref_camera_focusmode_default_array);
+            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
+                    mInitialParams, this, mirror,
+                    mActivity.getMainLooper(), mUI);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.v(TAG, "onConfigurationChanged");
+        setDisplayOrientation();
+    }
+
+    @Override
+    public void onActivityResult(
+            int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_CROP: {
+                Intent intent = new Intent();
+                if (data != null) {
+                    Bundle extras = data.getExtras();
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                }
+                mActivity.setResultEx(resultCode, intent);
+                mActivity.finish();
+
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+
+                break;
+            }
+        }
+    }
+
+    private boolean canTakePicture() {
+        return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
+    }
+
+    @Override
+    public void autoFocus() {
+        mFocusStartTime = System.currentTimeMillis();
+        mCameraDevice.autoFocus(mAutoFocusCallback);
+        setCameraState(FOCUSING);
+    }
+
+    @Override
+    public void cancelAutoFocus() {
+        mCameraDevice.cancelAutoFocus();
+        setCameraState(IDLE);
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    // Preview area is touched. Handle touch focus.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
+                || mCameraState == SNAPSHOT_IN_PROGRESS
+                || mCameraState == SWITCHING_CAMERA
+                || mCameraState == PREVIEW_STOPPED) {
+            return;
+        }
+
+        // Do not trigger touch focus if popup window is opened.
+        if (mUI.removeTopLevelPopup()) return;
+
+        // Check if metering area or focus area is supported.
+        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
+        mFocusManager.onSingleTapUp(x, y);
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        return mUI.onBackPressed();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+        case KeyEvent.KEYCODE_FOCUS:
+            if (mActivity.isInCameraApp() && mFirstTimeInitialized) {
+                if (event.getRepeatCount() == 0) {
+                    onShutterButtonFocus(true);
+                }
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_CAMERA:
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                onShutterButtonClick();
+            }
+            return true;
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+            // If we get a dpad center event without any focused view, move
+            // the focus to the shutter button and press it.
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                // Start auto-focus immediately to reduce shutter lag. After
+                // the shutter button gets the focus, onShutterButtonFocus()
+                // will be called again but it is fine.
+                if (mUI.removeTopLevelPopup()) return true;
+                onShutterButtonFocus(true);
+                mUI.pressShutterButton();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+            if (mActivity.isInCameraApp() && mFirstTimeInitialized) {
+                onShutterButtonClick();
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_FOCUS:
+            if (mFirstTimeInitialized) {
+                onShutterButtonFocus(false);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void closeCamera() {
+        if (mCameraDevice != null) {
+            mCameraDevice.setZoomChangeListener(null);
+            if(ApiHelper.HAS_FACE_DETECTION) {
+                mCameraDevice.setFaceDetectionListener(null);
+            }
+            mCameraDevice.setErrorCallback(null);
+            CameraHolder.instance().release();
+            mFaceDetectionStarted = false;
+            mCameraDevice = null;
+            setCameraState(PREVIEW_STOPPED);
+            mFocusManager.onCameraReleased();
+        }
+    }
+
+    private void setDisplayOrientation() {
+        mDisplayRotation = Util.getDisplayRotation(mActivity);
+        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+        mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
+        mUI.setDisplayOrientation(mDisplayOrientation);
+        if (mFocusManager != null) {
+            mFocusManager.setDisplayOrientation(mDisplayOrientation);
+        }
+        // GLRoot also uses the DisplayRotation, and needs to be told to layout to update
+        mActivity.getGLRoot().requestLayoutContentPane();
+    }
+
+    // Only called by UI thread.
+    private void setupPreview() {
+        mFocusManager.resetTouchFocus();
+        startPreview();
+        setCameraState(IDLE);
+        startFaceDetection();
+    }
+
+    // This can be called by UI Thread or CameraStartUpThread. So this should
+    // not modify the views.
+    private void startPreview() {
+        mCameraDevice.setErrorCallback(mErrorCallback);
+
+        // ICS camera frameworks has a bug. Face detection state is not cleared
+        // after taking a picture. Stop the preview to work around it. The bug
+        // was fixed in JB.
+        if (mCameraState != PREVIEW_STOPPED) stopPreview();
+
+        setDisplayOrientation();
+
+        if (!mSnapshotOnIdle) {
+            // If the focus mode is continuous autofocus, call cancelAutoFocus to
+            // resume it because it may have been paused by autoFocus call.
+            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+                mCameraDevice.cancelAutoFocus();
+            }
+            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+        }
+        setCameraParameters(UPDATE_PARAM_ALL);
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+            if (mUI.getSurfaceTexture() == null) {
+                Size size = mParameters.getPreviewSize();
+                if (mCameraDisplayOrientation % 180 == 0) {
+                    screenNail.setSize(size.width, size.height);
+                } else {
+                    screenNail.setSize(size.height, size.width);
+                }
+                screenNail.enableAspectRatioClamping();
+                mActivity.notifyScreenNailChanged();
+                screenNail.acquireSurfaceTexture();
+                CameraStartUpThread t = mCameraStartUpThread;
+                if (t != null && t.isCanceled()) {
+                    return; // Exiting, so no need to get the surface texture.
+                }
+                mUI.setSurfaceTexture(screenNail.getSurfaceTexture());
+            } else {
+                updatePreviewSize(screenNail);
+            }
+            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+            Object st = mUI.getSurfaceTexture();
+            if (st != null) {
+                mCameraDevice.setPreviewTextureAsync((SurfaceTexture) st);
+            }
+        } else {
+            mCameraDevice.setDisplayOrientation(mDisplayOrientation);
+            mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
+        }
+
+        Log.v(TAG, "startPreview");
+        mCameraDevice.startPreviewAsync();
+
+        mFocusManager.onPreviewStarted();
+
+        if (mSnapshotOnIdle) {
+            mHandler.post(mDoSnapRunnable);
+        }
+    }
+
+    private void updatePreviewSize(CameraScreenNail snail) {
+        Size size = mParameters.getPreviewSize();
+        int w = size.width;
+        int h = size.height;
+        if (mCameraDisplayOrientation % 180 != 0) {
+            w = size.height;
+            h = size.width;
+        }
+        if (snail.getWidth() != w || snail.getHeight() != h) {
+            snail.setSize(w, h);
+        }
+        snail.enableAspectRatioClamping();
+        mActivity.notifyScreenNailChanged();
+    }
+
+    @Override
+    public void stopPreview() {
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            Log.v(TAG, "stopPreview");
+            mCameraDevice.stopPreview();
+            mFaceDetectionStarted = false;
+        }
+        setCameraState(PREVIEW_STOPPED);
+        if (mFocusManager != null) mFocusManager.onPreviewStopped();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void updateCameraParametersInitialize() {
+        // Reset preview frame rate to the maximum because it may be lowered by
+        // video camera application.
+        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
+        if (frameRates != null) {
+            Integer max = Collections.max(frameRates);
+            mParameters.setPreviewFrameRate(max);
+        }
+
+        mParameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+        // Disable video stabilization. Convenience methods not available in API
+        // level <= 14
+        String vstabSupported = mParameters.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            mParameters.set("video-stabilization", "false");
+        }
+    }
+
+    private void updateCameraParametersZoom() {
+        // Set zoom.
+        if (mParameters.isZoomSupported()) {
+            mParameters.setZoom(mZoomValue);
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoExposureLockIfSupported() {
+        if (mAeLockSupported) {
+            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoWhiteBalanceLockIfSupported() {
+        if (mAwbLockSupported) {
+            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setFocusAreasIfSupported() {
+        if (mFocusAreaSupported) {
+            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setMeteringAreasIfSupported() {
+        if (mMeteringAreaSupported) {
+            // Use the same area for focus and metering.
+            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
+        }
+    }
+
+    private void updateCameraParametersPreference() {
+        setAutoExposureLockIfSupported();
+        setAutoWhiteBalanceLockIfSupported();
+        setFocusAreasIfSupported();
+        setMeteringAreasIfSupported();
+
+        // Set picture size.
+        String pictureSize = mPreferences.getString(
+                CameraSettings.KEY_PICTURE_SIZE, null);
+        if (pictureSize == null) {
+            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
+        } else {
+            List<Size> supported = mParameters.getSupportedPictureSizes();
+            CameraSettings.setCameraPictureSize(
+                    pictureSize, supported, mParameters);
+        }
+        Size size = mParameters.getPictureSize();
+
+        // Set a preview size that is closest to the viewfinder height and has
+        // the right aspect ratio.
+        List<Size> sizes = mParameters.getSupportedPreviewSizes();
+        Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+                (double) size.width / size.height);
+        Size original = mParameters.getPreviewSize();
+        if (!original.equals(optimalSize)) {
+            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+            // Zoom related settings will be changed for different preview
+            // sizes, so set and read the parameters to get latest values
+            if (mHandler.getLooper() == Looper.myLooper()) {
+                // On UI thread only, not when camera starts up
+                setupPreview();
+            } else {
+                mCameraDevice.setParameters(mParameters);
+            }
+            mParameters = mCameraDevice.getParameters();
+        }
+        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+
+        // Since changing scene mode may change supported values, set scene mode
+        // first. HDR is a scene mode. To promote it in UI, it is stored in a
+        // separate preference.
+        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
+                mActivity.getString(R.string.pref_camera_hdr_default));
+        if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
+            mSceneMode = Util.SCENE_MODE_HDR;
+        } else {
+            mSceneMode = mPreferences.getString(
+                CameraSettings.KEY_SCENE_MODE,
+                mActivity.getString(R.string.pref_camera_scenemode_default));
+        }
+        if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
+            if (!mParameters.getSceneMode().equals(mSceneMode)) {
+                mParameters.setSceneMode(mSceneMode);
+
+                // Setting scene mode will change the settings of flash mode,
+                // white balance, and focus mode. Here we read back the
+                // parameters, so we can know those settings.
+                mCameraDevice.setParameters(mParameters);
+                mParameters = mCameraDevice.getParameters();
+            }
+        } else {
+            mSceneMode = mParameters.getSceneMode();
+            if (mSceneMode == null) {
+                mSceneMode = Parameters.SCENE_MODE_AUTO;
+            }
+        }
+
+        // Set JPEG quality.
+        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+                CameraProfile.QUALITY_HIGH);
+        mParameters.setJpegQuality(jpegQuality);
+
+        // For the following settings, we need to check if the settings are
+        // still supported by latest driver, if not, ignore the settings.
+
+        // Set exposure compensation
+        int value = CameraSettings.readExposure(mPreferences);
+        int max = mParameters.getMaxExposureCompensation();
+        int min = mParameters.getMinExposureCompensation();
+        if (value >= min && value <= max) {
+            mParameters.setExposureCompensation(value);
+        } else {
+            Log.w(TAG, "invalid exposure range: " + value);
+        }
+
+        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            // Set flash mode.
+            String flashMode = mPreferences.getString(
+                    CameraSettings.KEY_FLASH_MODE,
+                    mActivity.getString(R.string.pref_camera_flashmode_default));
+            List<String> supportedFlash = mParameters.getSupportedFlashModes();
+            if (Util.isSupported(flashMode, supportedFlash)) {
+                mParameters.setFlashMode(flashMode);
+            } else {
+                flashMode = mParameters.getFlashMode();
+                if (flashMode == null) {
+                    flashMode = mActivity.getString(
+                            R.string.pref_camera_flashmode_no_flash);
+                }
+            }
+
+            // Set white balance parameter.
+            String whiteBalance = mPreferences.getString(
+                    CameraSettings.KEY_WHITE_BALANCE,
+                    mActivity.getString(R.string.pref_camera_whitebalance_default));
+            if (Util.isSupported(whiteBalance,
+                    mParameters.getSupportedWhiteBalance())) {
+                mParameters.setWhiteBalance(whiteBalance);
+            } else {
+                whiteBalance = mParameters.getWhiteBalance();
+                if (whiteBalance == null) {
+                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+                }
+            }
+
+            // Set focus mode.
+            mFocusManager.overrideFocusMode(null);
+            mParameters.setFocusMode(mFocusManager.getFocusMode());
+        } else {
+            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
+        }
+
+        if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
+            updateAutoFocusMoveCallback();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void updateAutoFocusMoveCallback() {
+        if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+            mCameraDevice.setAutoFocusMoveCallback(
+                (AutoFocusMoveCallback) mAutoFocusMoveCallback);
+        } else {
+            mCameraDevice.setAutoFocusMoveCallback(null);
+        }
+    }
+
+    // We separate the parameters into several subsets, so we can update only
+    // the subsets actually need updating. The PREFERENCE set needs extra
+    // locking because the preference can be changed from GLThread as well.
+    private void setCameraParameters(int updateSet) {
+        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
+            updateCameraParametersInitialize();
+        }
+
+        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
+            updateCameraParametersZoom();
+        }
+
+        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
+            updateCameraParametersPreference();
+        }
+
+        mCameraDevice.setParameters(mParameters);
+    }
+
+    // If the Camera is idle, update the parameters immediately, otherwise
+    // accumulate them in mUpdateSet and update later.
+    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
+        mUpdateSet |= additionalUpdateSet;
+        if (mCameraDevice == null) {
+            // We will update all the parameters when we open the device, so
+            // we don't need to do anything now.
+            mUpdateSet = 0;
+            return;
+        } else if (isCameraIdle()) {
+            setCameraParameters(mUpdateSet);
+            updateSceneMode();
+            mUpdateSet = 0;
+        } else {
+            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
+                mHandler.sendEmptyMessageDelayed(
+                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
+            }
+        }
+    }
+
+    public boolean isCameraIdle() {
+        return (mCameraState == IDLE) ||
+                (mCameraState == PREVIEW_STOPPED) ||
+                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
+                        && (mCameraState != SWITCHING_CAMERA));
+    }
+
+    public boolean isImageCaptureIntent() {
+        String action = mActivity.getIntent().getAction();
+        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+                || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
+    }
+
+    private void setupCaptureParams() {
+        Bundle myExtras = mActivity.getIntent().getExtras();
+        if (myExtras != null) {
+            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            mCropValue = myExtras.getString("crop");
+        }
+    }
+
+    @Override
+    public void onSharedPreferenceChanged() {
+        // ignore the events after "onPause()"
+        if (mPaused) return;
+
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
+        mUI.updateOnScreenIndicators(mParameters, mPreferenceGroup, mPreferences);
+    }
+
+    @Override
+    public void onCameraPickerClicked(int cameraId) {
+        if (mPaused || mPendingSwitchCameraId != -1) return;
+
+        mPendingSwitchCameraId = cameraId;
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            Log.v(TAG, "Start to copy texture. cameraId=" + cameraId);
+            // We need to keep a preview frame for the animation before
+            // releasing the camera. This will trigger onPreviewTextureCopied.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+            // Disable all camera controls.
+            setCameraState(SWITCHING_CAMERA);
+        } else {
+            switchCamera();
+        }
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mHandler.sendEmptyMessage(SWITCH_CAMERA);
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!mActivity.isFinishing()) keepScreenOnAwhile();
+    }
+
+    private void resetScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    // TODO: Delete this function after old camera code is removed
+    @Override
+    public void onRestorePreferencesClicked() {
+    }
+
+    @Override
+    public void onOverriddenPreferencesClicked() {
+        if (mPaused) return;
+        mUI.showPreferencesToast();
+    }
+
+    private void showTapToFocusToast() {
+        // TODO: Use a toast?
+        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
+        // Clear the preference.
+        Editor editor = mPreferences.edit();
+        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
+        editor.apply();
+    }
+
+    private void initializeCapabilities() {
+        mInitialParams = mCameraDevice.getParameters();
+        mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
+        mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
+        mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
+        mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
+        mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
+                Util.FOCUS_MODE_CONTINUOUS_PICTURE);
+    }
+
+    @Override
+    public void onCountDownFinished() {
+        mSnapshotOnIdle = false;
+        mFocusManager.doSnap();
+        mFocusManager.onShutterUp();
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return !mIsImageCaptureIntent;
+    }
+
+    @Override
+    public boolean needsPieMenu() {
+        return true;
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+        mUI.onShowSwitcherPopup();
+    }
+
+    @Override
+    public int onZoomChanged(int index) {
+        // Not useful to change zoom value when the activity is paused.
+        if (mPaused) return index;
+        mZoomValue = index;
+        if (mParameters == null || mCameraDevice == null) return index;
+        // Set zoom parameters asynchronously
+        mParameters.setZoom(mZoomValue);
+        mCameraDevice.setParameters(mParameters);
+        Parameters p = mCameraDevice.getParameters();
+        if (p != null) return p.getZoom();
+        return index;
+    }
+
+    @Override
+    public int getCameraState() {
+        return mCameraState;
+    }
+
+    @Override
+    public void onQueueStatus(boolean full) {
+        mUI.enableShutter(!full);
+    }
+
+    @Override
+    public void onMediaSaveServiceConnected(MediaSaveService s) {
+        // We set the listener only when both service and shutterbutton
+        // are initialized.
+        if (mFirstTimeInitialized) {
+            s.setListener(this);
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        int type = event.sensor.getType();
+        float[] data;
+        if (type == Sensor.TYPE_ACCELEROMETER) {
+            data = mGData;
+        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
+            data = mMData;
+        } else {
+            // we should not be here.
+            return;
+        }
+        for (int i = 0; i < 3 ; i++) {
+            data[i] = event.values[i];
+        }
+        float[] orientation = new float[3];
+        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
+        SensorManager.getOrientation(mR, orientation);
+        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
+        if (mHeading < 0) {
+            mHeading += 360;
+        }
+    }
+}
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
new file mode 100644
index 0000000..a364d79
--- /dev/null
+++ b/src/com/android/camera/PhotoUI.java
@@ -0,0 +1,691 @@
+/*
+ * 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.hardware.Camera;
+import android.hardware.Camera.Face;
+import android.hardware.Camera.FaceDetectionListener;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.Toast;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.FocusOverlayManager.FocusUI;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CountDownView;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.FaceView;
+import com.android.camera.ui.FocusIndicator;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.PieRenderer.PieListener;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.List;
+
+public class PhotoUI implements PieListener,
+    SurfaceHolder.Callback,
+    PreviewGestures.SingleTapListener,
+    FocusUI,
+    LocationManager.Listener,
+    FaceDetectionListener,
+    PreviewGestures.SwipeListener {
+
+    private static final String TAG = "CAM_UI";
+
+    private CameraActivity mActivity;
+    private PhotoController mController;
+    private PreviewGestures mGestures;
+
+    private View mRootView;
+    private Object mSurfaceTexture;
+    private volatile SurfaceHolder mSurfaceHolder;
+
+    private AbstractSettingPopup mPopup;
+    private ShutterButton mShutterButton;
+    private CountDownView mCountDownView;
+
+    private FaceView mFaceView;
+    private RenderOverlay mRenderOverlay;
+    private View mReviewCancelButton;
+    private View mReviewDoneButton;
+    private View mReviewRetakeButton;
+
+    private View mMenuButton;
+    private View mBlocker;
+    private PhotoMenu mMenu;
+
+    private OnScreenIndicators mOnScreenIndicators;
+
+    private PieRenderer mPieRenderer;
+    private ZoomRenderer mZoomRenderer;
+    private Toast mNotSelectableToast;
+
+    private int mZoomMax;
+    private List<Integer> mZoomRatios;
+
+    private int mPreviewWidth = 0;
+    private int mPreviewHeight = 0;
+    private View mPreviewThumb;
+
+    private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right,
+                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            int width = right - left;
+            int height = bottom - top;
+            // Full-screen screennail
+            int w = width;
+            int h = height;
+            if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+                w = height;
+                h = width;
+            }
+            if (mPreviewWidth != w || mPreviewHeight != h) {
+                mPreviewWidth = w;
+                mPreviewHeight = h;
+                mController.onScreenSizeChanged(width, height, w, h);
+            }
+        }
+    };
+
+    public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
+        mActivity = activity;
+        mController = controller;
+        mRootView = parent;
+
+        mActivity.getLayoutInflater().inflate(R.layout.photo_module,
+                (ViewGroup) mRootView, true);
+        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+
+        initIndicators();
+        mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
+        mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
+
+        if (ApiHelper.HAS_FACE_DETECTION) {
+            ViewStub faceViewStub = (ViewStub) mRootView
+                    .findViewById(R.id.face_view_stub);
+            if (faceViewStub != null) {
+                faceViewStub.inflate();
+                mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
+            }
+        }
+
+    }
+
+    public View getRootView() {
+        return mRootView;
+    }
+
+    private void initIndicators() {
+        mOnScreenIndicators = new OnScreenIndicators(mActivity,
+                mActivity.findViewById(R.id.on_screen_indicators));
+    }
+
+    public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
+            Camera.Parameters params, OnPreferenceChangedListener listener) {
+        if (mPieRenderer == null) {
+            mPieRenderer = new PieRenderer(mActivity);
+            mPieRenderer.setPieListener(this);
+            mRenderOverlay.addRenderer(mPieRenderer);
+        }
+        if (mMenu == null) {
+            mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
+            mMenu.setListener(listener);
+        }
+        mMenu.initialize(prefGroup);
+
+        if (mZoomRenderer == null) {
+            mZoomRenderer = new ZoomRenderer(mActivity);
+            mRenderOverlay.addRenderer(mZoomRenderer);
+        }
+        if (mGestures == null) {
+            // this will handle gesture disambiguation and dispatching
+            mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
+                    this);
+        }
+        mGestures.reset();
+        mGestures.setRenderOverlay(mRenderOverlay);
+        mGestures.addTouchReceiver(mMenuButton);
+        mGestures.addUnclickableArea(mBlocker);
+        enablePreviewThumb(false);
+        // make sure to add touch targets for image capture
+        if (mController.isImageCaptureIntent()) {
+            if (mReviewCancelButton != null) {
+                mGestures.addTouchReceiver(mReviewCancelButton);
+            }
+            if (mReviewDoneButton != null) {
+                mGestures.addTouchReceiver(mReviewDoneButton);
+            }
+        }
+        mRenderOverlay.requestLayout();
+
+        initializeZoom(params);
+        updateOnScreenIndicators(params, prefGroup, prefs);
+    }
+
+    private void openMenu() {
+        if (mPieRenderer != null) {
+            // If autofocus is not finished, cancel autofocus so that the
+            // subsequent touch can be handled by PreviewGestures
+            if (mController.getCameraState() == PhotoController.FOCUSING) {
+                    mController.cancelAutoFocus();
+            }
+            mPieRenderer.showInCenter();
+        }
+    }
+
+    public void initializeControlByIntent() {
+        mBlocker = mActivity.findViewById(R.id.blocker);
+        mPreviewThumb = mActivity.findViewById(R.id.preview_thumb);
+        mPreviewThumb.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mActivity.gotoGallery();
+            }
+        });
+        mMenuButton = mActivity.findViewById(R.id.menu);
+        mMenuButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                openMenu();
+            }
+        });
+        if (mController.isImageCaptureIntent()) {
+            mActivity.hideSwitcher();
+            ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
+            mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
+
+            mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+            mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+            mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
+            mReviewCancelButton.setVisibility(View.VISIBLE);
+
+            mReviewDoneButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureDone();
+                }
+            });
+            mReviewCancelButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureCancelled();
+                }
+            });
+
+            mReviewRetakeButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureRetake();
+                }
+            });
+        }
+    }
+
+    // called from onResume but only the first time
+    public  void initializeFirstTime() {
+        // Initialize shutter button.
+        mShutterButton = mActivity.getShutterButton();
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+        mShutterButton.setOnShutterButtonListener(mController);
+        mShutterButton.setVisibility(View.VISIBLE);
+        mRootView.addOnLayoutChangeListener(mLayoutListener);
+    }
+
+    // called from onResume every other time
+    public void initializeSecondTime(Camera.Parameters params) {
+        initializeZoom(params);
+        if (mController.isImageCaptureIntent()) {
+            hidePostCaptureAlert();
+        }
+        if (mMenu != null) {
+            mMenu.reloadPreferences();
+        }
+        mRootView.addOnLayoutChangeListener(mLayoutListener);
+    }
+
+    public void initializeZoom(Camera.Parameters params) {
+        if ((params == null) || !params.isZoomSupported()
+                || (mZoomRenderer == null)) return;
+        mZoomMax = params.getMaxZoom();
+        mZoomRatios = params.getZoomRatios();
+        // Currently we use immediate zoom for fast zooming to get better UX and
+        // there is no plan to take advantage of the smooth zoom.
+        if (mZoomRenderer != null) {
+            mZoomRenderer.setZoomMax(mZoomMax);
+            mZoomRenderer.setZoom(params.getZoom());
+            mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
+            mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+        }
+    }
+
+    public void enableGestures(boolean enable) {
+        if (mGestures != null) {
+            mGestures.setEnabled(enable);
+        }
+    }
+
+    @Override
+    public void showGpsOnScreenIndicator(boolean hasSignal) { }
+
+    @Override
+    public void hideGpsOnScreenIndicator() { }
+
+    public void overrideSettings(final String ... keyvalues) {
+        mMenu.overrideSettings(keyvalues);
+    }
+
+    public void updateOnScreenIndicators(Camera.Parameters params,
+            PreferenceGroup group, ComboPreferences prefs) {
+        if (params == null) return;
+        mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
+        mOnScreenIndicators.updateExposureOnScreenIndicator(params,
+                CameraSettings.readExposure(prefs));
+        mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
+        int wbIndex = 2;
+        ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
+        if (pref != null) {
+            wbIndex = pref.getCurrentIndex();
+        }
+        mOnScreenIndicators.updateWBIndicator(wbIndex);
+        boolean location = RecordLocationPreference.get(
+                prefs, mActivity.getContentResolver());
+        mOnScreenIndicators.updateLocationIndicator(location);
+    }
+
+    public void setCameraState(int state) {
+    }
+
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mGestures != null && mRenderOverlay != null) {
+            return mGestures.dispatchTouch(m);
+        }
+        return false;
+    }
+
+    public boolean onBackPressed() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+            return true;
+        }
+        // In image capture mode, back button should:
+        // 1) if there is any popup, dismiss them, 2) otherwise, get out of
+        // image capture
+        if (mController.isImageCaptureIntent()) {
+            if (!removeTopLevelPopup()) {
+                // no popup to dismiss, cancel image capture
+                mController.onCaptureCancelled();
+            }
+            return true;
+        } else if (!mController.isCameraIdle()) {
+            // ignore backs while we're taking a picture
+            return true;
+        } else {
+            return removeTopLevelPopup();
+        }
+    }
+
+    public void onFullScreenChanged(boolean full) {
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(!full);
+        }
+        if (mPopup != null) {
+            dismissPopup(full);
+        }
+        if (mGestures != null) {
+            mGestures.setEnabled(full);
+        }
+        if (mRenderOverlay != null) {
+            // this can not happen in capture mode
+            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (mPieRenderer != null) {
+            mPieRenderer.setBlockFocus(!full);
+        }
+        setShowMenu(full);
+        if (mBlocker != null) {
+            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
+    }
+
+    public void enablePreviewThumb(boolean enabled) {
+        if (enabled) {
+            mGestures.addTouchReceiver(mPreviewThumb);
+            mPreviewThumb.setVisibility(View.VISIBLE);
+        } else {
+            mGestures.removeTouchReceiver(mPreviewThumb);
+            mPreviewThumb.setVisibility(View.GONE);
+        }
+    }
+
+    public boolean removeTopLevelPopup() {
+        // Remove the top level popup or dialog box and return true if there's any
+        if (mPopup != null) {
+            dismissPopup();
+            return true;
+        }
+        return false;
+    }
+
+    public void showPopup(AbstractSettingPopup popup) {
+        mActivity.hideUI();
+        mBlocker.setVisibility(View.INVISIBLE);
+        setShowMenu(false);
+        mPopup = popup;
+        mPopup.setVisibility(View.VISIBLE);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.CENTER;
+        ((FrameLayout) mRootView).addView(mPopup, lp);
+        mGestures.addTouchReceiver(mPopup);
+    }
+
+    public void dismissPopup() {
+        dismissPopup(true);
+    }
+
+    private void dismissPopup(boolean fullScreen) {
+        if (fullScreen) {
+            mActivity.showUI();
+            mBlocker.setVisibility(View.VISIBLE);
+        }
+        setShowMenu(fullScreen);
+        if (mPopup != null) {
+            mGestures.removeTouchReceiver(mPopup);
+            ((FrameLayout) mRootView).removeView(mPopup);
+            mPopup = null;
+        }
+        mMenu.popupDismissed();
+    }
+
+    public void onShowSwitcherPopup() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+        }
+    }
+
+    private void setShowMenu(boolean show) {
+        if (mOnScreenIndicators != null) {
+            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+        if (mMenuButton != null) {
+            mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public boolean collapseCameraControls() {
+        // Remove all the popups/dialog boxes
+        boolean ret = false;
+        if (mPopup != null) {
+            dismissPopup();
+            ret = true;
+        }
+        onShowSwitcherPopup();
+        return ret;
+    }
+
+    protected void showPostCaptureAlert() {
+        mOnScreenIndicators.setVisibility(View.GONE);
+        mMenuButton.setVisibility(View.GONE);
+        Util.fadeIn(mReviewDoneButton);
+        mShutterButton.setVisibility(View.INVISIBLE);
+        Util.fadeIn(mReviewRetakeButton);
+        pauseFaceDetection();
+    }
+
+    protected void hidePostCaptureAlert() {
+        mOnScreenIndicators.setVisibility(View.VISIBLE);
+        mMenuButton.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewDoneButton);
+        mShutterButton.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewRetakeButton);
+        resumeFaceDetection();
+    }
+
+    public void setDisplayOrientation(int orientation) {
+        if (mFaceView != null) {
+            mFaceView.setDisplayOrientation(orientation);
+        }
+    }
+
+    // shutter button handling
+
+    public boolean isShutterPressed() {
+        return mShutterButton.isPressed();
+    }
+
+    // focus handling
+
+
+    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
+        @Override
+        public void onZoomValueChanged(int index) {
+            int newZoom = mController.onZoomChanged(index);
+            if (mZoomRenderer != null) {
+                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
+            }
+        }
+
+        @Override
+        public void onZoomStart() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(true);
+            }
+        }
+
+        @Override
+        public void onZoomEnd() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(false);
+            }
+        }
+    }
+
+    @Override
+    public void onPieOpened(int centerX, int centerY) {
+        dismissPopup();
+        mActivity.cancelActivityTouchHandling();
+        mActivity.setSwipingEnabled(false);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(true);
+        }
+    }
+
+    @Override
+    public void onPieClosed() {
+        mActivity.setSwipingEnabled(true);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(false);
+        }
+    }
+
+    // Surface Listener
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height="
+                + height);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.v(TAG, "surfaceCreated: " + holder);
+        mSurfaceHolder = holder;
+        mController.onSurfaceCreated(holder);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.v(TAG, "surfaceDestroyed: " + holder);
+        mSurfaceHolder = null;
+        mController.stopPreview();
+    }
+
+    public Object getSurfaceTexture() {
+        return mSurfaceTexture;
+    }
+
+    public void setSurfaceTexture(Object st) {
+        mSurfaceTexture = st;
+    }
+
+    public SurfaceHolder getSurfaceHolder() {
+        return mSurfaceHolder;
+    }
+
+    public boolean isCountingDown() {
+        return mCountDownView.isCountingDown();
+    }
+
+    public void cancelCountDown() {
+        mCountDownView.cancelCountDown();
+    }
+
+    public void startCountDown(int sec, boolean playSound) {
+        mCountDownView.startCountDown(sec, playSound);
+    }
+
+    public void showPreferencesToast() {
+        if (mNotSelectableToast == null) {
+            String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
+            mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
+        }
+        mNotSelectableToast.show();
+    }
+
+    public void onPause() {
+        mCountDownView.cancelCountDown();
+        // Close the camera now because other activities may need to use it.
+        mSurfaceTexture = null;
+
+        // Clear UI.
+        collapseCameraControls();
+        if (mFaceView != null) mFaceView.clear();
+
+
+        mRootView.removeOnLayoutChangeListener(mLayoutListener);
+        mPreviewWidth = 0;
+        mPreviewHeight = 0;
+    }
+
+    public void enableShutter(boolean enabled) {
+        if (mShutterButton != null) {
+            mShutterButton.setEnabled(enabled);
+        }
+    }
+
+    public void pressShutterButton() {
+        if (mShutterButton.isInTouchMode()) {
+            mShutterButton.requestFocusFromTouch();
+        } else {
+            mShutterButton.requestFocus();
+        }
+        mShutterButton.setPressed(true);
+    }
+
+    // forward from preview gestures to controller
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        mController.onSingleTapUp(view, x, y);
+    }
+
+    // focus UI implementation
+
+    private FocusIndicator getFocusIndicator() {
+        return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
+    }
+
+    @Override
+    public boolean hasFaces() {
+        return (mFaceView != null && mFaceView.faceExists());
+    }
+
+    public void clearFaces() {
+        if (mFaceView != null) mFaceView.clear();
+    }
+
+    @Override
+    public void clearFocus() {
+        FocusIndicator indicator = getFocusIndicator();
+        if (indicator != null) indicator.clear();
+    }
+
+    @Override
+    public void setFocusPosition(int x, int y) {
+        mPieRenderer.setFocus(x, y);
+    }
+
+    @Override
+    public void onFocusStarted() {
+        getFocusIndicator().showStart();
+    }
+
+    @Override
+    public void onFocusSucceeded(boolean timeout) {
+        getFocusIndicator().showSuccess(timeout);
+    }
+
+    @Override
+    public void onFocusFailed(boolean timeout) {
+        getFocusIndicator().showFail(timeout);
+    }
+
+    @Override
+    public void pauseFaceDetection() {
+        if (mFaceView != null) mFaceView.pause();
+    }
+
+    @Override
+    public void resumeFaceDetection() {
+        if (mFaceView != null) mFaceView.resume();
+    }
+
+    public void onStartFaceDetection(int orientation, boolean mirror) {
+        mFaceView.clear();
+        mFaceView.setVisibility(View.VISIBLE);
+        mFaceView.setDisplayOrientation(orientation);
+        mFaceView.setMirror(mirror);
+        mFaceView.resume();
+    }
+
+    @Override
+    public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
+        mFaceView.setFaces(faces);
+    }
+
+    @Override
+    public void onSwipe(int direction) {
+        if (direction == PreviewGestures.DIR_UP) {
+            openMenu();
+        }
+    }
+
+}
diff --git a/src/com/android/camera/PieController.java b/src/com/android/camera/PieController.java
new file mode 100644
index 0000000..3cbcb4b
--- /dev/null
+++ b/src/com/android/camera/PieController.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 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.app.Activity;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.drawable.TextDrawable;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PieController {
+
+    private static String TAG = "CAM_piecontrol";
+
+    protected static final int MODE_PHOTO = 0;
+    protected static final int MODE_VIDEO = 1;
+
+    protected static float CENTER = (float) Math.PI / 2;
+    protected static final float SWEEP = 0.06f;
+
+    protected Activity mActivity;
+    protected PreferenceGroup mPreferenceGroup;
+    protected OnPreferenceChangedListener mListener;
+    protected PieRenderer mRenderer;
+    private List<IconListPreference> mPreferences;
+    private Map<IconListPreference, PieItem> mPreferenceMap;
+    private Map<IconListPreference, String> mOverrides;
+
+    public void setListener(OnPreferenceChangedListener listener) {
+        mListener = listener;
+    }
+
+    public PieController(Activity activity, PieRenderer pie) {
+        mActivity = activity;
+        mRenderer = pie;
+        mPreferences = new ArrayList<IconListPreference>();
+        mPreferenceMap = new HashMap<IconListPreference, PieItem>();
+        mOverrides = new HashMap<IconListPreference, String>();
+    }
+
+    public void initialize(PreferenceGroup group) {
+        mRenderer.clearItems();
+        mPreferenceMap.clear();
+        setPreferenceGroup(group);
+    }
+
+    public void onSettingChanged(ListPreference pref) {
+        if (mListener != null) {
+            mListener.onSharedPreferenceChanged();
+        }
+    }
+
+    protected void setCameraId(int cameraId) {
+        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+        pref.setValue("" + cameraId);
+    }
+
+    protected PieItem makeItem(int resId) {
+        // We need a mutable version as we change the alpha
+        Drawable d = mActivity.getResources().getDrawable(resId).mutate();
+        return new PieItem(d, 0);
+    }
+
+    protected PieItem makeItem(CharSequence value) {
+        TextDrawable drawable = new TextDrawable(mActivity.getResources(), value);
+        return new PieItem(drawable, 0);
+    }
+
+    public PieItem makeItem(String prefKey) {
+        final IconListPreference pref =
+                (IconListPreference) mPreferenceGroup.findPreference(prefKey);
+        if (pref == null) return null;
+        int[] iconIds = pref.getLargeIconIds();
+        int resid = -1;
+        if (!pref.getUseSingleIcon() && iconIds != null) {
+            // Each entry has a corresponding icon.
+            int index = pref.findIndexOfValue(pref.getValue());
+            resid = iconIds[index];
+        } else {
+            // The preference only has a single icon to represent it.
+            resid = pref.getSingleIcon();
+        }
+        PieItem item = makeItem(resid);
+        item.setLabel(pref.getTitle().toUpperCase());
+        mPreferences.add(pref);
+        mPreferenceMap.put(pref, item);
+        int nOfEntries = pref.getEntries().length;
+        if (nOfEntries > 1) {
+            for (int i = 0; i < nOfEntries; i++) {
+                PieItem inner = null;
+                if (iconIds != null) {
+                    inner = makeItem(iconIds[i]);
+                } else {
+                    inner = makeItem(pref.getEntries()[i]);
+                }
+                inner.setLabel(pref.getLabels()[i]);
+                item.addItem(inner);
+                final int index = i;
+                inner.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(PieItem item) {
+                        pref.setValueIndex(index);
+                        reloadPreference(pref);
+                        onSettingChanged(pref);
+                    }
+                });
+            }
+        }
+        return item;
+    }
+
+    public PieItem makeSwitchItem(final String prefKey, boolean addListener) {
+        final IconListPreference pref =
+                (IconListPreference) mPreferenceGroup.findPreference(prefKey);
+        if (pref == null) return null;
+        int[] iconIds = pref.getLargeIconIds();
+        int resid = -1;
+        int index = pref.findIndexOfValue(pref.getValue());
+        if (!pref.getUseSingleIcon() && iconIds != null) {
+            // Each entry has a corresponding icon.
+            resid = iconIds[index];
+        } else {
+            // The preference only has a single icon to represent it.
+            resid = pref.getSingleIcon();
+        }
+        PieItem item = makeItem(resid);
+        item.setLabel(pref.getLabels()[index]);
+        item.setImageResource(mActivity, resid);
+        mPreferences.add(pref);
+        mPreferenceMap.put(pref, item);
+        if (addListener) {
+            final PieItem fitem = item;
+            item.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(PieItem item) {
+                    IconListPreference pref = (IconListPreference) mPreferenceGroup
+                            .findPreference(prefKey);
+                    int index = pref.findIndexOfValue(pref.getValue());
+                    CharSequence[] values = pref.getEntryValues();
+                    index = (index + 1) % values.length;
+                    pref.setValueIndex(index);
+                    fitem.setLabel(pref.getLabels()[index]);
+                    fitem.setImageResource(mActivity,
+                            ((IconListPreference) pref).getLargeIconIds()[index]);
+                    reloadPreference(pref);
+                    onSettingChanged(pref);
+                }
+            });
+        }
+        return item;
+    }
+
+
+    public PieItem makeDialItem(ListPreference pref, int iconId, float center, float sweep) {
+        PieItem item = makeItem(iconId);
+        return item;
+    }
+
+    public void addItem(String prefKey) {
+        PieItem item = makeItem(prefKey);
+        mRenderer.addItem(item);
+    }
+
+    public void updateItem(PieItem item, String prefKey) {
+        IconListPreference pref = (IconListPreference) mPreferenceGroup
+                .findPreference(prefKey);
+        if (pref != null) {
+            int index = pref.findIndexOfValue(pref.getValue());
+            item.setLabel(pref.getLabels()[index]);
+            item.setImageResource(mActivity,
+                    ((IconListPreference) pref).getLargeIconIds()[index]);
+        }
+    }
+
+    public void setPreferenceGroup(PreferenceGroup group) {
+        mPreferenceGroup = group;
+    }
+
+    public void reloadPreferences() {
+        mPreferenceGroup.reloadValue();
+        for (IconListPreference pref : mPreferenceMap.keySet()) {
+            reloadPreference(pref);
+        }
+    }
+
+    private void reloadPreference(IconListPreference pref) {
+        if (pref.getUseSingleIcon()) return;
+        PieItem item = mPreferenceMap.get(pref);
+        String overrideValue = mOverrides.get(pref);
+        int[] iconIds = pref.getLargeIconIds();
+        if (iconIds != null) {
+            // Each entry has a corresponding icon.
+            int index;
+            if (overrideValue == null) {
+                index = pref.findIndexOfValue(pref.getValue());
+            } else {
+                index = pref.findIndexOfValue(overrideValue);
+                if (index == -1) {
+                    // Avoid the crash if camera driver has bugs.
+                    Log.e(TAG, "Fail to find override value=" + overrideValue);
+                    pref.print();
+                    return;
+                }
+            }
+            item.setImageResource(mActivity, iconIds[index]);
+        } else {
+            // The preference only has a single icon to represent it.
+            item.setImageResource(mActivity, pref.getSingleIcon());
+        }
+    }
+
+    // Scene mode may override other camera settings (ex: flash mode).
+    public void overrideSettings(final String ... keyvalues) {
+        if (keyvalues.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        for (IconListPreference pref : mPreferenceMap.keySet()) {
+            override(pref, keyvalues);
+        }
+    }
+
+    private void override(IconListPreference pref, final String ... keyvalues) {
+        mOverrides.remove(pref);
+        for (int i = 0; i < keyvalues.length; i += 2) {
+            String key = keyvalues[i];
+            String value = keyvalues[i + 1];
+            if (key.equals(pref.getKey())) {
+                mOverrides.put(pref, value);
+                PieItem item = mPreferenceMap.get(pref);
+                item.setEnabled(value == null);
+                break;
+            }
+        }
+        reloadPreference(pref);
+    }
+}
diff --git a/src/com/android/camera/PreferenceGroup.java b/src/com/android/camera/PreferenceGroup.java
new file mode 100644
index 0000000..4d0519f
--- /dev/null
+++ b/src/com/android/camera/PreferenceGroup.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import java.util.ArrayList;
+
+/**
+ * A collection of <code>CameraPreference</code>s. It may contain other
+ * <code>PreferenceGroup</code> and form a tree structure.
+ */
+public class PreferenceGroup extends CameraPreference {
+    private ArrayList<CameraPreference> list =
+            new ArrayList<CameraPreference>();
+
+    public PreferenceGroup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void addChild(CameraPreference child) {
+        list.add(child);
+    }
+
+    public void removePreference(int index) {
+        list.remove(index);
+    }
+
+    public CameraPreference get(int index) {
+        return list.get(index);
+    }
+
+    public int size() {
+        return list.size();
+    }
+
+    @Override
+    public void reloadValue() {
+        for (CameraPreference pref : list) {
+            pref.reloadValue();
+        }
+    }
+
+    /**
+     * Finds the preference with the given key recursively. Returns
+     * <code>null</code> if cannot find.
+     */
+    public ListPreference findPreference(String key) {
+        // Find a leaf preference with the given key. Currently, the base
+        // type of all "leaf" preference is "ListPreference". If we add some
+        // other types later, we need to change the code.
+        for (CameraPreference pref : list) {
+            if (pref instanceof ListPreference) {
+                ListPreference listPref = (ListPreference) pref;
+                if(listPref.getKey().equals(key)) return listPref;
+            } else if(pref instanceof PreferenceGroup) {
+                ListPreference listPref =
+                        ((PreferenceGroup) pref).findPreference(key);
+                if (listPref != null) return listPref;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/camera/PreferenceInflater.java b/src/com/android/camera/PreferenceInflater.java
new file mode 100644
index 0000000..231c983
--- /dev/null
+++ b/src/com/android/camera/PreferenceInflater.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 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.Context;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.InflateException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Inflate <code>CameraPreference</code> from XML resource.
+ */
+public class PreferenceInflater {
+    private static final String PACKAGE_NAME =
+            PreferenceInflater.class.getPackage().getName();
+
+    private static final Class<?>[] CTOR_SIGNATURE =
+            new Class[] {Context.class, AttributeSet.class};
+    private static final HashMap<String, Constructor<?>> sConstructorMap =
+            new HashMap<String, Constructor<?>>();
+
+    private Context mContext;
+
+    public PreferenceInflater(Context context) {
+        mContext = context;
+    }
+
+    public CameraPreference inflate(int resId) {
+        return inflate(mContext.getResources().getXml(resId));
+    }
+
+    private CameraPreference newPreference(String tagName, Object[] args) {
+        String name = PACKAGE_NAME + "." + tagName;
+        Constructor<?> constructor = sConstructorMap.get(name);
+        try {
+            if (constructor == null) {
+                // Class not found in the cache, see if it's real, and try to
+                // add it
+                Class<?> clazz = mContext.getClassLoader().loadClass(name);
+                constructor = clazz.getConstructor(CTOR_SIGNATURE);
+                sConstructorMap.put(name, constructor);
+            }
+            return (CameraPreference) constructor.newInstance(args);
+        } catch (NoSuchMethodException e) {
+            throw new InflateException("Error inflating class " + name, e);
+        } catch (ClassNotFoundException e) {
+            throw new InflateException("No such class: " + name, e);
+        } catch (Exception e) {
+            throw new InflateException("While create instance of" + name, e);
+        }
+    }
+
+    private CameraPreference inflate(XmlPullParser parser) {
+
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        ArrayList<CameraPreference> list = new ArrayList<CameraPreference>();
+        Object args[] = new Object[]{mContext, attrs};
+
+        try {
+            for (int type = parser.next();
+                    type != XmlPullParser.END_DOCUMENT; type = parser.next()) {
+                if (type != XmlPullParser.START_TAG) continue;
+                CameraPreference pref = newPreference(parser.getName(), args);
+
+                int depth = parser.getDepth();
+                if (depth > list.size()) {
+                    list.add(pref);
+                } else {
+                    list.set(depth - 1, pref);
+                }
+                if (depth > 1) {
+                    ((PreferenceGroup) list.get(depth - 2)).addChild(pref);
+                }
+            }
+
+            if (list.size() == 0) {
+                throw new InflateException("No root element found");
+            }
+            return list.get(0);
+        } catch (XmlPullParserException e) {
+            throw new InflateException(e);
+        } catch (IOException e) {
+            throw new InflateException(parser.getPositionDescription(), e);
+        }
+    }
+}
diff --git a/src/com/android/camera/PreviewFrameLayout.java b/src/com/android/camera/PreviewFrameLayout.java
new file mode 100644
index 0000000..03ef91c
--- /dev/null
+++ b/src/com/android/camera/PreviewFrameLayout.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.RelativeLayout;
+
+import com.android.camera.ui.LayoutChangeHelper;
+import com.android.camera.ui.LayoutChangeNotifier;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+/**
+ * A layout which handles the preview aspect ratio.
+ */
+public class PreviewFrameLayout extends RelativeLayout implements LayoutChangeNotifier {
+
+    private static final String TAG = "CAM_preview";
+
+    /** A callback to be invoked when the preview frame's size changes. */
+    public interface OnSizeChangedListener {
+        public void onSizeChanged(int width, int height);
+    }
+
+    private double mAspectRatio;
+    private View mBorder;
+    private OnSizeChangedListener mListener;
+    private LayoutChangeHelper mLayoutChangeHelper;
+
+    public PreviewFrameLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setAspectRatio(4.0 / 3.0);
+        mLayoutChangeHelper = new LayoutChangeHelper(this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        mBorder = findViewById(R.id.preview_border);
+    }
+
+    public void setAspectRatio(double ratio) {
+        if (ratio <= 0.0) throw new IllegalArgumentException();
+
+        if (mAspectRatio != ratio) {
+            mAspectRatio = ratio;
+            requestLayout();
+        }
+    }
+
+    public void showBorder(boolean enabled) {
+        mBorder.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    public void fadeOutBorder() {
+        Util.fadeOut(mBorder);
+    }
+
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        int previewWidth = MeasureSpec.getSize(widthSpec);
+        int previewHeight = MeasureSpec.getSize(heightSpec);
+
+        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Get the padding of the border background.
+            int hPadding = getPaddingLeft() + getPaddingRight();
+            int vPadding = getPaddingTop() + getPaddingBottom();
+
+            // Resize the preview frame with correct aspect ratio.
+            previewWidth -= hPadding;
+            previewHeight -= vPadding;
+
+            boolean widthLonger = previewWidth > previewHeight;
+            int longSide = (widthLonger ? previewWidth : previewHeight);
+            int shortSide = (widthLonger ? previewHeight : previewWidth);
+            if (longSide > shortSide * mAspectRatio) {
+                longSide = (int) ((double) shortSide * mAspectRatio);
+            } else {
+                shortSide = (int) ((double) longSide / mAspectRatio);
+            }
+            if (widthLonger) {
+                previewWidth = longSide;
+                previewHeight = shortSide;
+            } else {
+                previewWidth = shortSide;
+                previewHeight = longSide;
+            }
+
+            // Add the padding of the border.
+            previewWidth += hPadding;
+            previewHeight += vPadding;
+        }
+
+        // Ask children to follow the new preview dimension.
+        super.onMeasure(MeasureSpec.makeMeasureSpec(previewWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(previewHeight, MeasureSpec.EXACTLY));
+    }
+
+    public void setOnSizeChangedListener(OnSizeChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        if (mListener != null) mListener.onSizeChanged(w, h);
+    }
+
+    @Override
+    public void setOnLayoutChangeListener(
+            LayoutChangeNotifier.Listener listener) {
+        mLayoutChangeHelper.setOnLayoutChangeListener(listener);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mLayoutChangeHelper.onLayout(changed, l, t, r, b);
+    }
+}
diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java
new file mode 100644
index 0000000..0b80ff6
--- /dev/null
+++ b/src/com/android/camera/PreviewGestures.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2012 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.os.Handler;
+import android.os.Message;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PreviewGestures
+        implements ScaleGestureDetector.OnScaleGestureListener {
+
+    private static final String TAG = "CAM_gestures";
+
+    private static final long TIMEOUT_PIE = 200;
+    private static final int MSG_PIE = 1;
+    private static final int MODE_NONE = 0;
+    private static final int MODE_PIE = 1;
+    private static final int MODE_ZOOM = 2;
+    private static final int MODE_MODULE = 3;
+    private static final int MODE_ALL = 4;
+    private static final int MODE_SWIPE = 5;
+
+    public static final int DIR_UP = 0;
+    public static final int DIR_DOWN = 1;
+    public static final int DIR_LEFT = 2;
+    public static final int DIR_RIGHT = 3;
+
+    private CameraActivity mActivity;
+    private SingleTapListener mTapListener;
+    private RenderOverlay mOverlay;
+    private PieRenderer mPie;
+    private ZoomRenderer mZoom;
+    private MotionEvent mDown;
+    private MotionEvent mCurrent;
+    private ScaleGestureDetector mScale;
+    private List<View> mReceivers;
+    private List<View> mUnclickableAreas;
+    private int mMode;
+    private int mSlop;
+    private int mTapTimeout;
+    private boolean mEnabled;
+    private boolean mZoomOnly;
+    private int mOrientation;
+    private int[] mLocation;
+    private SwipeListener mSwipeListener;
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_PIE) {
+                mMode = MODE_PIE;
+                openPie();
+                cancelActivityTouchHandling(mDown);
+            }
+        }
+    };
+
+    public interface SingleTapListener {
+        public void onSingleTapUp(View v, int x, int y);
+    }
+
+    interface SwipeListener {
+        public void onSwipe(int direction);
+    }
+
+    public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener,
+            ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) {
+        mActivity = ctx;
+        mTapListener = tapListener;
+        mPie = pie;
+        mZoom = zoom;
+        mMode = MODE_ALL;
+        mScale = new ScaleGestureDetector(ctx, this);
+        mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        mEnabled = true;
+        mLocation = new int[2];
+        mSwipeListener = swipe;
+    }
+
+    public void setRenderOverlay(RenderOverlay overlay) {
+        mOverlay = overlay;
+    }
+
+    public void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    public void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+        if (!enabled) {
+            cancelPie();
+        }
+    }
+
+    public void setZoomOnly(boolean zoom) {
+        mZoomOnly = zoom;
+    }
+
+    public void addTouchReceiver(View v) {
+        if (mReceivers == null) {
+            mReceivers = new ArrayList<View>();
+        }
+        mReceivers.add(v);
+    }
+
+    public void removeTouchReceiver(View v) {
+        if (mReceivers == null || v == null) return;
+        mReceivers.remove(v);
+    }
+
+    public void addUnclickableArea(View v) {
+        if (mUnclickableAreas == null) {
+            mUnclickableAreas = new ArrayList<View>();
+        }
+        mUnclickableAreas.add(v);
+    }
+
+    public void clearTouchReceivers() {
+        if (mReceivers != null) {
+            mReceivers.clear();
+        }
+    }
+
+    public void clearUnclickableAreas() {
+        if (mUnclickableAreas != null) {
+            mUnclickableAreas.clear();
+        }
+    }
+
+    private boolean checkClickable(MotionEvent m) {
+        if (mUnclickableAreas != null) {
+            for (View v : mUnclickableAreas) {
+                if (isInside(m, v)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    public void reset() {
+        clearTouchReceivers();
+        clearUnclickableAreas();
+    }
+
+    public boolean dispatchTouch(MotionEvent m) {
+        if (!mEnabled) {
+            return mActivity.superDispatchTouchEvent(m);
+        }
+        mCurrent = m;
+        if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
+            if (checkReceivers(m)) {
+                mMode = MODE_MODULE;
+                return mActivity.superDispatchTouchEvent(m);
+            } else {
+                mMode = MODE_ALL;
+                mDown = MotionEvent.obtain(m);
+                if (mPie != null && mPie.showsItems()) {
+                    mMode = MODE_PIE;
+                    return sendToPie(m);
+                }
+                if (mPie != null && !mZoomOnly && checkClickable(m)) {
+                    mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
+                }
+                if (mZoom != null) {
+                    mScale.onTouchEvent(m);
+                }
+                // make sure this is ok
+                return mActivity.superDispatchTouchEvent(m);
+            }
+        } else if (mMode == MODE_NONE) {
+            return false;
+        } else if (mMode == MODE_SWIPE) {
+            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+                mSwipeListener.onSwipe(getSwipeDirection(m));
+            }
+            return true;
+        } else if (mMode == MODE_PIE) {
+            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+                sendToPie(makeCancelEvent(m));
+                if (mZoom != null) {
+                    onScaleBegin(mScale);
+                }
+            } else {
+                return sendToPie(m);
+            }
+            return true;
+        } else if (mMode == MODE_ZOOM) {
+            mScale.onTouchEvent(m);
+            if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+                mMode = MODE_NONE;
+                onScaleEnd(mScale);
+            }
+            return true;
+        } else if (mMode == MODE_MODULE) {
+            return mActivity.superDispatchTouchEvent(m);
+        } else {
+            // didn't receive down event previously;
+            // assume module wasn't initialzed and ignore this event.
+            if (mDown == null) {
+                return true;
+            }
+            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+                if (!mZoomOnly) {
+                    cancelPie();
+                    sendToPie(makeCancelEvent(m));
+                }
+                if (mZoom != null) {
+                    mScale.onTouchEvent(m);
+                    onScaleBegin(mScale);
+                }
+            } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
+                    && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+                // user initiated and stopped zoom gesture without zooming
+                mScale.onTouchEvent(m);
+                onScaleEnd(mScale);
+            }
+            // not zoom or pie mode and no timeout yet
+            if (mZoom != null) {
+                boolean res = mScale.onTouchEvent(m);
+                if (mScale.isInProgress()) {
+                    cancelPie();
+                    cancelActivityTouchHandling(m);
+                    return res;
+                }
+            }
+            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+                cancelPie();
+                // must have been tap
+                if (m.getEventTime() - mDown.getEventTime() < mTapTimeout
+                        && checkClickable(m)) {
+                    cancelActivityTouchHandling(m);
+                    mTapListener.onSingleTapUp(null,
+                            (int) mDown.getX() - mOverlay.getWindowPositionX(),
+                            (int) mDown.getY() - mOverlay.getWindowPositionY());
+                    return true;
+                } else {
+                    return mActivity.superDispatchTouchEvent(m);
+                }
+            } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
+                if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
+                        || Math.abs(m.getY() - mDown.getY()) > mSlop) {
+                    // moved too far and no timeout yet, no focus or pie
+                    cancelPie();
+                    int dir = getSwipeDirection(m);
+                    if (dir == DIR_LEFT) {
+                        mMode = MODE_MODULE;
+                        return mActivity.superDispatchTouchEvent(m);
+                    } else {
+                        cancelActivityTouchHandling(m);
+                        mMode = MODE_NONE;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    private boolean checkReceivers(MotionEvent m) {
+        if (mReceivers != null) {
+            for (View receiver : mReceivers) {
+                if (isInside(m, receiver)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // left tests for finger moving right to left
+    private int getSwipeDirection(MotionEvent m) {
+        float dx = 0;
+        float dy = 0;
+        switch (mOrientation) {
+        case 0:
+            dx = m.getX() - mDown.getX();
+            dy = m.getY() - mDown.getY();
+            break;
+        case 90:
+            dx = - (m.getY() - mDown.getY());
+            dy = m.getX() - mDown.getX();
+            break;
+        case 180:
+            dx = -(m.getX() - mDown.getX());
+            dy = m.getY() - mDown.getY();
+            break;
+        case 270:
+            dx = m.getY() - mDown.getY();
+            dy = m.getX() - mDown.getX();
+            break;
+        }
+        if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT;
+        if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT;
+        if (dy > 0) return DIR_DOWN;
+        return DIR_UP;
+    }
+
+    private boolean isInside(MotionEvent evt, View v) {
+        v.getLocationInWindow(mLocation);
+        // when view is flipped horizontally
+        if ((int) v.getRotationY() == 180) {
+            mLocation[0] -= v.getWidth();
+        }
+        // when view is flipped vertically
+        if ((int) v.getRotationX() == 180) {
+            mLocation[1] -= v.getHeight();
+        }
+        return (v.getVisibility() == View.VISIBLE
+                && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
+                && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
+    }
+
+    public void cancelActivityTouchHandling(MotionEvent m) {
+        mActivity.superDispatchTouchEvent(makeCancelEvent(m));
+    }
+
+    private MotionEvent makeCancelEvent(MotionEvent m) {
+        MotionEvent c = MotionEvent.obtain(m);
+        c.setAction(MotionEvent.ACTION_CANCEL);
+        return c;
+    }
+
+    private void openPie() {
+        mDown.offsetLocation(-mOverlay.getWindowPositionX(),
+                -mOverlay.getWindowPositionY());
+        mOverlay.directDispatchTouch(mDown, mPie);
+    }
+
+    private void cancelPie() {
+        mHandler.removeMessages(MSG_PIE);
+    }
+
+    private boolean sendToPie(MotionEvent m) {
+        m.offsetLocation(-mOverlay.getWindowPositionX(),
+                -mOverlay.getWindowPositionY());
+        return mOverlay.directDispatchTouch(m, mPie);
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        return mZoom.onScale(detector);
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        if (mMode != MODE_ZOOM) {
+            mMode = MODE_ZOOM;
+            cancelActivityTouchHandling(mCurrent);
+        }
+        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+            return mZoom.onScaleBegin(detector);
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+            mZoom.onScaleEnd(detector);
+        }
+    }
+}
diff --git a/src/com/android/camera/ProxyLauncher.java b/src/com/android/camera/ProxyLauncher.java
new file mode 100644
index 0000000..8c56621
--- /dev/null
+++ b/src/com/android/camera/ProxyLauncher.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+public class ProxyLauncher extends Activity {
+
+    public static final int RESULT_USER_CANCELED = -2;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (savedInstanceState == null) {
+                Intent intent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
+                startActivityForResult(intent, 0);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (resultCode == RESULT_CANCELED) {
+            resultCode = RESULT_USER_CANCELED;
+        }
+        setResult(resultCode, data);
+        finish();
+    }
+
+}
diff --git a/src/com/android/camera/RecordLocationPreference.java b/src/com/android/camera/RecordLocationPreference.java
new file mode 100644
index 0000000..9992afa
--- /dev/null
+++ b/src/com/android/camera/RecordLocationPreference.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.AttributeSet;
+
+/**
+ * {@code RecordLocationPreference} is used to keep the "store locaiton"
+ * option in {@code SharedPreference}.
+ */
+public class RecordLocationPreference extends IconListPreference {
+
+    public static final String VALUE_NONE = "none";
+    public static final String VALUE_ON = "on";
+    public static final String VALUE_OFF = "off";
+
+    private final ContentResolver mResolver;
+
+    public RecordLocationPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mResolver = context.getContentResolver();
+    }
+
+    @Override
+    public String getValue() {
+        return get(getSharedPreferences(), mResolver) ? VALUE_ON : VALUE_OFF;
+    }
+
+    public static boolean get(
+            SharedPreferences pref, ContentResolver resolver) {
+        String value = pref.getString(
+                CameraSettings.KEY_RECORD_LOCATION, VALUE_NONE);
+        return VALUE_ON.equals(value);
+    }
+
+    public static boolean isSet(SharedPreferences pref) {
+        String value = pref.getString(
+                CameraSettings.KEY_RECORD_LOCATION, VALUE_NONE);
+        return !VALUE_NONE.equals(value);
+    }
+}
diff --git a/src/com/android/camera/RotateDialogController.java b/src/com/android/camera/RotateDialogController.java
new file mode 100644
index 0000000..5d5e5e7
--- /dev/null
+++ b/src/com/android/camera/RotateDialogController.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2011 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.app.Activity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.camera.ui.Rotatable;
+import com.android.camera.ui.RotateLayout;
+import com.android.gallery3d.R;
+
+public class RotateDialogController implements Rotatable {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "RotateDialogController";
+    private static final long ANIM_DURATION = 150;  // millis
+
+    private Activity mActivity;
+    private int mLayoutResourceID;
+    private View mDialogRootLayout;
+    private RotateLayout mRotateDialog;
+    private View mRotateDialogTitleLayout;
+    private View mRotateDialogButtonLayout;
+    private TextView mRotateDialogTitle;
+    private ProgressBar mRotateDialogSpinner;
+    private TextView mRotateDialogText;
+    private TextView mRotateDialogButton1;
+    private TextView mRotateDialogButton2;
+
+    private Animation mFadeInAnim, mFadeOutAnim;
+
+    public RotateDialogController(Activity a, int layoutResource) {
+        mActivity = a;
+        mLayoutResourceID = layoutResource;
+    }
+
+    private void inflateDialogLayout() {
+        if (mDialogRootLayout == null) {
+            ViewGroup layoutRoot = (ViewGroup) mActivity.getWindow().getDecorView();
+            LayoutInflater inflater = mActivity.getLayoutInflater();
+            View v = inflater.inflate(mLayoutResourceID, layoutRoot);
+            mDialogRootLayout = v.findViewById(R.id.rotate_dialog_root_layout);
+            mRotateDialog = (RotateLayout) v.findViewById(R.id.rotate_dialog_layout);
+            mRotateDialogTitleLayout = v.findViewById(R.id.rotate_dialog_title_layout);
+            mRotateDialogButtonLayout = v.findViewById(R.id.rotate_dialog_button_layout);
+            mRotateDialogTitle = (TextView) v.findViewById(R.id.rotate_dialog_title);
+            mRotateDialogSpinner = (ProgressBar) v.findViewById(R.id.rotate_dialog_spinner);
+            mRotateDialogText = (TextView) v.findViewById(R.id.rotate_dialog_text);
+            mRotateDialogButton1 = (Button) v.findViewById(R.id.rotate_dialog_button1);
+            mRotateDialogButton2 = (Button) v.findViewById(R.id.rotate_dialog_button2);
+
+            mFadeInAnim = AnimationUtils.loadAnimation(
+                    mActivity, android.R.anim.fade_in);
+            mFadeOutAnim = AnimationUtils.loadAnimation(
+                    mActivity, android.R.anim.fade_out);
+            mFadeInAnim.setDuration(ANIM_DURATION);
+            mFadeOutAnim.setDuration(ANIM_DURATION);
+        }
+    }
+
+    @Override
+    public void setOrientation(int orientation, boolean animation) {
+        inflateDialogLayout();
+        mRotateDialog.setOrientation(orientation, animation);
+    }
+
+    public void resetRotateDialog() {
+        inflateDialogLayout();
+        mRotateDialogTitleLayout.setVisibility(View.GONE);
+        mRotateDialogSpinner.setVisibility(View.GONE);
+        mRotateDialogButton1.setVisibility(View.GONE);
+        mRotateDialogButton2.setVisibility(View.GONE);
+        mRotateDialogButtonLayout.setVisibility(View.GONE);
+    }
+
+    private void fadeOutDialog() {
+        mDialogRootLayout.startAnimation(mFadeOutAnim);
+        mDialogRootLayout.setVisibility(View.GONE);
+    }
+
+    private void fadeInDialog() {
+        mDialogRootLayout.startAnimation(mFadeInAnim);
+        mDialogRootLayout.setVisibility(View.VISIBLE);
+    }
+
+    public void dismissDialog() {
+        if (mDialogRootLayout != null && mDialogRootLayout.getVisibility() != View.GONE) {
+            fadeOutDialog();
+        }
+    }
+
+    public void showAlertDialog(String title, String msg, String button1Text,
+                final Runnable r1, String button2Text, final Runnable r2) {
+        resetRotateDialog();
+
+        if (title != null) {
+            mRotateDialogTitle.setText(title);
+            mRotateDialogTitleLayout.setVisibility(View.VISIBLE);
+        }
+
+        mRotateDialogText.setText(msg);
+
+        if (button1Text != null) {
+            mRotateDialogButton1.setText(button1Text);
+            mRotateDialogButton1.setContentDescription(button1Text);
+            mRotateDialogButton1.setVisibility(View.VISIBLE);
+            mRotateDialogButton1.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (r1 != null) r1.run();
+                    dismissDialog();
+                }
+            });
+            mRotateDialogButtonLayout.setVisibility(View.VISIBLE);
+        }
+        if (button2Text != null) {
+            mRotateDialogButton2.setText(button2Text);
+            mRotateDialogButton2.setContentDescription(button2Text);
+            mRotateDialogButton2.setVisibility(View.VISIBLE);
+            mRotateDialogButton2.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (r2 != null) r2.run();
+                    dismissDialog();
+                }
+            });
+            mRotateDialogButtonLayout.setVisibility(View.VISIBLE);
+        }
+
+        fadeInDialog();
+    }
+
+    public void showWaitingDialog(String msg) {
+        resetRotateDialog();
+
+        mRotateDialogText.setText(msg);
+        mRotateDialogSpinner.setVisibility(View.VISIBLE);
+
+        fadeInDialog();
+    }
+
+    public int getVisibility() {
+        if (mDialogRootLayout != null) {
+            return mDialogRootLayout.getVisibility();
+        }
+        return View.INVISIBLE;
+    }
+}
diff --git a/src/com/android/camera/SecureCameraActivity.java b/src/com/android/camera/SecureCameraActivity.java
new file mode 100644
index 0000000..2fa68f8
--- /dev/null
+++ b/src/com/android/camera/SecureCameraActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 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;
+
+// Use a different activity for secure camera only. So it can have a different
+// task affinity from others. This makes sure non-secure camera activity is not
+// started in secure lock screen.
+public class SecureCameraActivity extends CameraActivity {
+}
diff --git a/src/com/android/camera/ShutterButton.java b/src/com/android/camera/ShutterButton.java
new file mode 100755
index 0000000..a1bbb1a
--- /dev/null
+++ b/src/com/android/camera/ShutterButton.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2008 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.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+
+/**
+ * A button designed to be used for the on-screen shutter button.
+ * It's currently an {@code ImageView} that can call a delegate when the
+ * pressed state changes.
+ */
+public class ShutterButton extends ImageView {
+
+    private boolean mTouchEnabled = true;
+
+    /**
+     * A callback to be invoked when a ShutterButton's pressed state changes.
+     */
+    public interface OnShutterButtonListener {
+        /**
+         * Called when a ShutterButton has been pressed.
+         *
+         * @param pressed The ShutterButton that was pressed.
+         */
+        void onShutterButtonFocus(boolean pressed);
+        void onShutterButtonClick();
+    }
+
+    private OnShutterButtonListener mListener;
+    private boolean mOldPressed;
+
+    public ShutterButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setOnShutterButtonListener(OnShutterButtonListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mTouchEnabled) {
+            return super.dispatchTouchEvent(m);
+        } else {
+            return false;
+        }
+    }
+
+    public void enableTouch(boolean enable) {
+        mTouchEnabled = enable;
+    }
+
+    /**
+     * Hook into the drawable state changing to get changes to isPressed -- the
+     * onPressed listener doesn't always get called when the pressed state
+     * changes.
+     */
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        final boolean pressed = isPressed();
+        if (pressed != mOldPressed) {
+            if (!pressed) {
+                // When pressing the physical camera button the sequence of
+                // events is:
+                //    focus pressed, optional camera pressed, focus released.
+                // We want to emulate this sequence of events with the shutter
+                // button. When clicking using a trackball button, the view
+                // system changes the drawable state before posting click
+                // notification, so the sequence of events is:
+                //    pressed(true), optional click, pressed(false)
+                // When clicking using touch events, the view system changes the
+                // drawable state after posting click notification, so the
+                // sequence of events is:
+                //    pressed(true), pressed(false), optional click
+                // Since we're emulating the physical camera button, we want to
+                // have the same order of events. So we want the optional click
+                // callback to be delivered before the pressed(false) callback.
+                //
+                // To do this, we delay the posting of the pressed(false) event
+                // slightly by pushing it on the event queue. This moves it
+                // after the optional click notification, so our client always
+                // sees events in this sequence:
+                //     pressed(true), optional click, pressed(false)
+                post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callShutterButtonFocus(pressed);
+                    }
+                });
+            } else {
+                callShutterButtonFocus(pressed);
+            }
+            mOldPressed = pressed;
+        }
+    }
+
+    private void callShutterButtonFocus(boolean pressed) {
+        if (mListener != null) {
+            mListener.onShutterButtonFocus(pressed);
+        }
+    }
+
+    @Override
+    public boolean performClick() {
+        boolean result = super.performClick();
+        if (mListener != null && getVisibility() == View.VISIBLE) {
+            mListener.onShutterButtonClick();
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/camera/SoundClips.java b/src/com/android/camera/SoundClips.java
new file mode 100644
index 0000000..8155c03
--- /dev/null
+++ b/src/com/android/camera/SoundClips.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2012 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.annotation.TargetApi;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaActionSound;
+import android.media.SoundPool;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+/*
+ * This class controls the sound playback according to the API level.
+ */
+public class SoundClips {
+    // Sound actions.
+    public static final int FOCUS_COMPLETE = 0;
+    public static final int START_VIDEO_RECORDING = 1;
+    public static final int STOP_VIDEO_RECORDING = 2;
+
+    public interface Player {
+        public void release();
+        public void play(int action);
+    }
+
+    public static Player getPlayer(Context context) {
+        if (ApiHelper.HAS_MEDIA_ACTION_SOUND) {
+            return new MediaActionSoundPlayer();
+        } else {
+            return new SoundPoolPlayer(context);
+        }
+    }
+
+    public static int getAudioTypeForSoundPool() {
+        return ApiHelper.getIntFieldIfExists(AudioManager.class,
+                "STREAM_SYSTEM_ENFORCED", null, AudioManager.STREAM_RING);
+    }
+
+    /**
+     * This class implements SoundClips.Player using MediaActionSound,
+     * which exists since API level 16.
+     */
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private static class MediaActionSoundPlayer implements Player {
+        private static final String TAG = "MediaActionSoundPlayer";
+        private MediaActionSound mSound;
+
+        @Override
+        public void release() {
+            if (mSound != null) {
+                mSound.release();
+                mSound = null;
+            }
+        }
+
+        public MediaActionSoundPlayer() {
+            mSound = new MediaActionSound();
+            mSound.load(MediaActionSound.START_VIDEO_RECORDING);
+            mSound.load(MediaActionSound.STOP_VIDEO_RECORDING);
+            mSound.load(MediaActionSound.FOCUS_COMPLETE);
+        }
+
+        @Override
+        public synchronized void play(int action) {
+            switch(action) {
+                case FOCUS_COMPLETE:
+                    mSound.play(MediaActionSound.FOCUS_COMPLETE);
+                    break;
+                case START_VIDEO_RECORDING:
+                    mSound.play(MediaActionSound.START_VIDEO_RECORDING);
+                    break;
+                case STOP_VIDEO_RECORDING:
+                    mSound.play(MediaActionSound.STOP_VIDEO_RECORDING);
+                    break;
+                default:
+                    Log.w(TAG, "Unrecognized action:" + action);
+            }
+        }
+    }
+
+    /**
+     * This class implements SoundClips.Player using SoundPool, which
+     * exists since API level 1.
+     */
+    private static class SoundPoolPlayer implements
+            Player, SoundPool.OnLoadCompleteListener {
+
+        private static final String TAG = "SoundPoolPlayer";
+        private static final int NUM_SOUND_STREAMS = 1;
+        private static final int[] SOUND_RES = { // Soundtrack res IDs.
+            R.raw.focus_complete,
+            R.raw.video_record
+        };
+
+        // ID returned by load() should be non-zero.
+        private static final int ID_NOT_LOADED = 0;
+
+        // Maps a sound action to the id;
+        private final int[] mSoundRes = {0, 1, 1};
+        // Store the context for lazy loading.
+        private Context mContext;
+        // mSoundPool is created every time load() is called and cleared every
+        // time release() is called.
+        private SoundPool mSoundPool;
+        // Sound ID of each sound resources. Given when the sound is loaded.
+        private final int[] mSoundIDs;
+        private final boolean[] mSoundIDReady;
+        private int mSoundIDToPlay;
+
+        public SoundPoolPlayer(Context context) {
+            mContext = context;
+
+            mSoundIDToPlay = ID_NOT_LOADED;
+
+            mSoundPool = new SoundPool(NUM_SOUND_STREAMS, getAudioTypeForSoundPool(), 0);
+            mSoundPool.setOnLoadCompleteListener(this);
+
+            mSoundIDs = new int[SOUND_RES.length];
+            mSoundIDReady = new boolean[SOUND_RES.length];
+            for (int i = 0; i < SOUND_RES.length; i++) {
+                mSoundIDs[i] = mSoundPool.load(mContext, SOUND_RES[i], 1);
+                mSoundIDReady[i] = false;
+            }
+        }
+
+        @Override
+        public synchronized void release() {
+            if (mSoundPool != null) {
+                mSoundPool.release();
+                mSoundPool = null;
+            }
+        }
+
+        @Override
+        public synchronized void play(int action) {
+            if (action < 0 || action >= mSoundRes.length) {
+                Log.e(TAG, "Resource ID not found for action:" + action + " in play().");
+                return;
+            }
+
+            int index = mSoundRes[action];
+            if (mSoundIDs[index] == ID_NOT_LOADED) {
+                // Not loaded yet, load first and then play when the loading is complete.
+                mSoundIDs[index] = mSoundPool.load(mContext, SOUND_RES[index], 1);
+                mSoundIDToPlay = mSoundIDs[index];
+            } else if (!mSoundIDReady[index]) {
+                // Loading and not ready yet.
+                mSoundIDToPlay = mSoundIDs[index];
+            } else {
+                mSoundPool.play(mSoundIDs[index], 1f, 1f, 0, 0, 1f);
+            }
+        }
+
+        @Override
+        public void onLoadComplete(SoundPool pool, int soundID, int status) {
+            if (status != 0) {
+                Log.e(TAG, "loading sound tracks failed (status=" + status + ")");
+                for (int i = 0; i < mSoundIDs.length; i++ ) {
+                    if (mSoundIDs[i] == soundID) {
+                        mSoundIDs[i] = ID_NOT_LOADED;
+                        break;
+                    }
+                }
+                return;
+            }
+
+            for (int i = 0; i < mSoundIDs.length; i++ ) {
+                if (mSoundIDs[i] == soundID) {
+                    mSoundIDReady[i] = true;
+                    break;
+                }
+            }
+
+            if (soundID == mSoundIDToPlay) {
+                mSoundIDToPlay = ID_NOT_LOADED;
+                mSoundPool.play(soundID, 1f, 1f, 0, 0, 1f);
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/StaticBitmapScreenNail.java b/src/com/android/camera/StaticBitmapScreenNail.java
new file mode 100644
index 0000000..10788c0
--- /dev/null
+++ b/src/com/android/camera/StaticBitmapScreenNail.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 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.graphics.Bitmap;
+
+import com.android.gallery3d.ui.BitmapScreenNail;
+
+public class StaticBitmapScreenNail extends BitmapScreenNail {
+    public StaticBitmapScreenNail(Bitmap bitmap) {
+        super(bitmap);
+    }
+
+    @Override
+    public void recycle() {
+        // Always keep the bitmap in memory.
+    }
+}
diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java
new file mode 100644
index 0000000..ba995ed
--- /dev/null
+++ b/src/com/android/camera/Storage.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 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.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.StatFs;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.MediaColumns;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+public class Storage {
+    private static final String TAG = "CameraStorage";
+
+    public static final String DCIM =
+            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString();
+
+    public static final String DIRECTORY = DCIM + "/Camera";
+
+    // Match the code in MediaProvider.computeBucketValues().
+    public static final String BUCKET_ID =
+            String.valueOf(DIRECTORY.toLowerCase().hashCode());
+
+    public static final long UNAVAILABLE = -1L;
+    public static final long PREPARING = -2L;
+    public static final long UNKNOWN_SIZE = -3L;
+    public static final long LOW_STORAGE_THRESHOLD = 50000000;
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    private static void setImageSize(ContentValues values, int width, int height) {
+        // The two fields are available since ICS but got published in JB
+        if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
+            values.put(MediaColumns.WIDTH, width);
+            values.put(MediaColumns.HEIGHT, height);
+        }
+    }
+
+    public static void writeFile(String path, byte[] data) {
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(path);
+            out.write(data);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to write data", e);
+        } finally {
+            try {
+                out.close();
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    // Save the image and add it to media store.
+    public static Uri addImage(ContentResolver resolver, String title,
+            long date, Location location, int orientation, ExifInterface exif,
+            byte[] jpeg, int width, int height) {
+        // Save the image.
+        String path = generateFilepath(title);
+        if (exif != null) {
+            try {
+                exif.writeExif(jpeg, path);
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to write data", e);
+            }
+        } else {
+            writeFile(path, jpeg);
+        }
+        return addImage(resolver, title, date, location, orientation,
+                jpeg.length, path, width, height);
+    }
+
+    // Add the image to media store.
+    public static Uri addImage(ContentResolver resolver, String title,
+            long date, Location location, int orientation, int jpegLength,
+            String path, int width, int height) {
+        // Insert into MediaStore.
+        ContentValues values = new ContentValues(9);
+        values.put(ImageColumns.TITLE, title);
+        values.put(ImageColumns.DISPLAY_NAME, title + ".jpg");
+        values.put(ImageColumns.DATE_TAKEN, date);
+        values.put(ImageColumns.MIME_TYPE, "image/jpeg");
+        // Clockwise rotation in degrees. 0, 90, 180, or 270.
+        values.put(ImageColumns.ORIENTATION, orientation);
+        values.put(ImageColumns.DATA, path);
+        values.put(ImageColumns.SIZE, jpegLength);
+
+        setImageSize(values, width, height);
+
+        if (location != null) {
+            values.put(ImageColumns.LATITUDE, location.getLatitude());
+            values.put(ImageColumns.LONGITUDE, location.getLongitude());
+        }
+
+        Uri uri = null;
+        try {
+            uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values);
+        } catch (Throwable th)  {
+            // This can happen when the external volume is already mounted, but
+            // MediaScanner has not notify MediaProvider to add that volume.
+            // The picture is still safe and MediaScanner will find it and
+            // insert it into MediaProvider. The only problem is that the user
+            // cannot click the thumbnail to review the picture.
+            Log.e(TAG, "Failed to write MediaStore" + th);
+        }
+        return uri;
+    }
+
+    public static void deleteImage(ContentResolver resolver, Uri uri) {
+        try {
+            resolver.delete(uri, null, null);
+        } catch (Throwable th) {
+            Log.e(TAG, "Failed to delete image: " + uri);
+        }
+    }
+
+    public static String generateFilepath(String title) {
+        return DIRECTORY + '/' + title + ".jpg";
+    }
+
+    public static long getAvailableSpace() {
+        String state = Environment.getExternalStorageState();
+        Log.d(TAG, "External storage state=" + state);
+        if (Environment.MEDIA_CHECKING.equals(state)) {
+            return PREPARING;
+        }
+        if (!Environment.MEDIA_MOUNTED.equals(state)) {
+            return UNAVAILABLE;
+        }
+
+        File dir = new File(DIRECTORY);
+        dir.mkdirs();
+        if (!dir.isDirectory() || !dir.canWrite()) {
+            return UNAVAILABLE;
+        }
+
+        try {
+            StatFs stat = new StatFs(DIRECTORY);
+            return stat.getAvailableBlocks() * (long) stat.getBlockSize();
+        } catch (Exception e) {
+            Log.i(TAG, "Fail to access external storage", e);
+        }
+        return UNKNOWN_SIZE;
+    }
+
+    /**
+     * OSX requires plugged-in USB storage to have path /DCIM/NNNAAAAA to be
+     * imported. This is a temporary fix for bug#1655552.
+     */
+    public static void ensureOSXCompatible() {
+        File nnnAAAAA = new File(DCIM, "100ANDRO");
+        if (!(nnnAAAAA.exists() || nnnAAAAA.mkdirs())) {
+            Log.e(TAG, "Failed to create " + nnnAAAAA.getPath());
+        }
+    }
+}
diff --git a/src/com/android/camera/SwitchAnimManager.java b/src/com/android/camera/SwitchAnimManager.java
new file mode 100644
index 0000000..6ec8822
--- /dev/null
+++ b/src/com/android/camera/SwitchAnimManager.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2012 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.os.SystemClock;
+import android.util.Log;
+
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
+
+/**
+ * Class to handle the animation when switching between back and front cameras.
+ * An image of the previous camera zooms in and fades out. The preview of the
+ * new camera zooms in and fades in. The image of the previous camera is called
+ * review in this class.
+ */
+public class SwitchAnimManager {
+    private static final String TAG = "SwitchAnimManager";
+    // The amount of change for zooming in and out.
+    private static final float ZOOM_DELTA_PREVIEW = 0.2f;
+    private static final float ZOOM_DELTA_REVIEW = 0.5f;
+    private static final float ANIMATION_DURATION = 400;  // ms
+    public static final float INITIAL_DARKEN_ALPHA = 0.8f;
+
+    private long mAnimStartTime;  // milliseconds.
+    // The drawing width and height of the review image. This is saved when the
+    // texture is copied.
+    private int mReviewDrawingWidth;
+    private int mReviewDrawingHeight;
+    // The maximum width of the camera screen nail width from onDraw. We need to
+    // know how much the preview is scaled and scale the review the same amount.
+    // For example, the preview is not full screen in film strip mode.
+    private int mPreviewFrameLayoutWidth;
+
+    public SwitchAnimManager() {
+    }
+
+    public void setReviewDrawingSize(int width, int height) {
+        mReviewDrawingWidth = width;
+        mReviewDrawingHeight = height;
+    }
+
+    // width: the width of PreviewFrameLayout view.
+    // height: the height of PreviewFrameLayout view. Not used. Kept for
+    //         consistency.
+    public void setPreviewFrameLayoutSize(int width, int height) {
+        mPreviewFrameLayoutWidth = width;
+    }
+
+    // w and h: the rectangle area where the animation takes place.
+    public void startAnimation() {
+        mAnimStartTime = SystemClock.uptimeMillis();
+    }
+
+    // Returns true if the animation has been drawn.
+    // preview: camera preview view.
+    // review: snapshot of the preview before switching the camera.
+    public boolean drawAnimation(GLCanvas canvas, int x, int y, int width,
+            int height, CameraScreenNail preview, RawTexture review) {
+        long timeDiff = SystemClock.uptimeMillis() - mAnimStartTime;
+        if (timeDiff > ANIMATION_DURATION) return false;
+        float fraction = timeDiff / ANIMATION_DURATION;
+
+        // Calculate the position and the size of the preview.
+        float centerX = x + width / 2f;
+        float centerY = y + height / 2f;
+        float previewAnimScale = 1 - ZOOM_DELTA_PREVIEW * (1 - fraction);
+        float previewWidth = width * previewAnimScale;
+        float previewHeight = height * previewAnimScale;
+        int previewX = Math.round(centerX - previewWidth / 2);
+        int previewY = Math.round(centerY - previewHeight / 2);
+
+        // Calculate the position and the size of the review.
+        float reviewAnimScale = 1 + ZOOM_DELTA_REVIEW * fraction;
+
+        // Calculate how much preview is scaled.
+        // The scaling is done by PhotoView in Gallery so we don't have the
+        // scaling information but only the width and the height passed to this
+        // method. The inference of the scale ratio is done by matching the
+        // current width and the original width we have at first when the camera
+        // layout is inflated.
+        float scaleRatio = 1;
+        if (mPreviewFrameLayoutWidth != 0) {
+            scaleRatio = (float) width / mPreviewFrameLayoutWidth;
+        } else {
+            Log.e(TAG, "mPreviewFrameLayoutWidth is 0.");
+        }
+        float reviewWidth = mReviewDrawingWidth * reviewAnimScale * scaleRatio;
+        float reviewHeight = mReviewDrawingHeight * reviewAnimScale * scaleRatio;
+        int reviewX = Math.round(centerX - reviewWidth / 2);
+        int reviewY = Math.round(centerY - reviewHeight / 2);
+
+        // Draw the preview.
+        float alpha = canvas.getAlpha();
+        canvas.setAlpha(fraction); // fade in
+        preview.directDraw(canvas, previewX, previewY, Math.round(previewWidth),
+                Math.round(previewHeight));
+
+        // Draw the review.
+        canvas.setAlpha((1f - fraction) * INITIAL_DARKEN_ALPHA); // fade out
+        review.draw(canvas, reviewX, reviewY, Math.round(reviewWidth),
+                Math.round(reviewHeight));
+        canvas.setAlpha(alpha);
+        return true;
+    }
+
+    public boolean drawDarkPreview(GLCanvas canvas, int x, int y, int width,
+            int height, RawTexture review) {
+        // Calculate the position and the size.
+        float centerX = x + width / 2f;
+        float centerY = y + height / 2f;
+        float scaleRatio = 1;
+        if (mPreviewFrameLayoutWidth != 0) {
+            scaleRatio = (float) width / mPreviewFrameLayoutWidth;
+        } else {
+            Log.e(TAG, "mPreviewFrameLayoutWidth is 0.");
+        }
+        float reviewWidth = mReviewDrawingWidth * scaleRatio;
+        float reviewHeight = mReviewDrawingHeight * scaleRatio;
+        int reviewX = Math.round(centerX - reviewWidth / 2);
+        int reviewY = Math.round(centerY - reviewHeight / 2);
+
+        // Draw the review.
+        float alpha = canvas.getAlpha();
+        canvas.setAlpha(INITIAL_DARKEN_ALPHA);
+        review.draw(canvas, reviewX, reviewY, Math.round(reviewWidth),
+                Math.round(reviewHeight));
+        canvas.setAlpha(alpha);
+        return true;
+    }
+
+}
diff --git a/src/com/android/camera/Thumbnail.java b/src/com/android/camera/Thumbnail.java
new file mode 100644
index 0000000..5f8483d
--- /dev/null
+++ b/src/com/android/camera/Thumbnail.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 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.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+
+import java.io.FileDescriptor;
+
+public class Thumbnail {
+    public static Bitmap createVideoThumbnailBitmap(FileDescriptor fd, int targetWidth) {
+        return createVideoThumbnailBitmap(null, fd, targetWidth);
+    }
+
+    public static Bitmap createVideoThumbnailBitmap(String filePath, int targetWidth) {
+        return createVideoThumbnailBitmap(filePath, null, targetWidth);
+    }
+
+    private static Bitmap createVideoThumbnailBitmap(String filePath, FileDescriptor fd,
+            int targetWidth) {
+        Bitmap bitmap = null;
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        try {
+            if (filePath != null) {
+                retriever.setDataSource(filePath);
+            } else {
+                retriever.setDataSource(fd);
+            }
+            bitmap = retriever.getFrameAtTime(-1);
+        } catch (IllegalArgumentException ex) {
+            // Assume this is a corrupt video file
+        } catch (RuntimeException ex) {
+            // Assume this is a corrupt video file.
+        } finally {
+            try {
+                retriever.release();
+            } catch (RuntimeException ex) {
+                // Ignore failures while cleaning up.
+            }
+        }
+        if (bitmap == null) return null;
+
+        // Scale down the bitmap if it is bigger than we need.
+        int width = bitmap.getWidth();
+        int height = bitmap.getHeight();
+        if (width > targetWidth) {
+            float scale = (float) targetWidth / width;
+            int w = Math.round(scale * width);
+            int h = Math.round(scale * height);
+            bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java
new file mode 100644
index 0000000..ed42de8
--- /dev/null
+++ b/src/com/android/camera/Util.java
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.hardware.Camera;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.telephony.TelephonyManager;
+import android.util.DisplayMetrics;
+import android.util.FloatMath;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Collection of utility functions used in this package.
+ */
+public class Util {
+    private static final String TAG = "Util";
+
+    // Orientation hysteresis amount used in rounding, in degrees
+    public static final int ORIENTATION_HYSTERESIS = 5;
+
+    public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
+    // See android.hardware.Camera.ACTION_NEW_PICTURE.
+    public static final String ACTION_NEW_PICTURE = "android.hardware.action.NEW_PICTURE";
+    // See android.hardware.Camera.ACTION_NEW_VIDEO.
+    public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+
+    // Fields from android.hardware.Camera.Parameters
+    public static final String FOCUS_MODE_CONTINUOUS_PICTURE = "continuous-picture";
+    public static final String RECORDING_HINT = "recording-hint";
+    private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
+    private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
+    private static final String VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
+    public static final String SCENE_MODE_HDR = "hdr";
+    public static final String TRUE = "true";
+    public static final String FALSE = "false";
+
+    public static boolean isSupported(String value, List<String> supported) {
+        return supported == null ? false : supported.indexOf(value) >= 0;
+    }
+
+    public static boolean isAutoExposureLockSupported(Parameters params) {
+        return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
+    }
+
+    public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
+        return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
+    }
+
+    public static boolean isVideoSnapshotSupported(Parameters params) {
+        return TRUE.equals(params.get(VIDEO_SNAPSHOT_SUPPORTED));
+    }
+
+    public static boolean isCameraHdrSupported(Parameters params) {
+        List<String> supported = params.getSupportedSceneModes();
+        return (supported != null) && supported.contains(SCENE_MODE_HDR);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    public static boolean isMeteringAreaSupported(Parameters params) {
+        if (ApiHelper.HAS_CAMERA_METERING_AREA) {
+            return params.getMaxNumMeteringAreas() > 0;
+        }
+        return false;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    public static boolean isFocusAreaSupported(Parameters params) {
+        if (ApiHelper.HAS_CAMERA_FOCUS_AREA) {
+            return (params.getMaxNumFocusAreas() > 0
+                    && isSupported(Parameters.FOCUS_MODE_AUTO,
+                            params.getSupportedFocusModes()));
+        }
+        return false;
+    }
+
+    // Private intent extras. Test only.
+    private static final String EXTRAS_CAMERA_FACING =
+            "android.intent.extras.CAMERA_FACING";
+
+    private static float sPixelDensity = 1;
+    private static ImageFileNamer sImageFileNamer;
+
+    private Util() {
+    }
+
+    public static void initialize(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        sPixelDensity = metrics.density;
+        sImageFileNamer = new ImageFileNamer(
+                context.getString(R.string.image_file_name_format));
+    }
+
+    public static int dpToPixel(int dp) {
+        return Math.round(sPixelDensity * dp);
+    }
+
+    // Rotates the bitmap by the specified degree.
+    // If a new bitmap is created, the original bitmap is recycled.
+    public static Bitmap rotate(Bitmap b, int degrees) {
+        return rotateAndMirror(b, degrees, false);
+    }
+
+    // Rotates and/or mirrors the bitmap. If a new bitmap is created, the
+    // original bitmap is recycled.
+    public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) {
+        if ((degrees != 0 || mirror) && b != null) {
+            Matrix m = new Matrix();
+            // Mirror first.
+            // horizontal flip + rotation = -rotation + horizontal flip
+            if (mirror) {
+                m.postScale(-1, 1);
+                degrees = (degrees + 360) % 360;
+                if (degrees == 0 || degrees == 180) {
+                    m.postTranslate(b.getWidth(), 0);
+                } else if (degrees == 90 || degrees == 270) {
+                    m.postTranslate(b.getHeight(), 0);
+                } else {
+                    throw new IllegalArgumentException("Invalid degrees=" + degrees);
+                }
+            }
+            if (degrees != 0) {
+                // clockwise
+                m.postRotate(degrees,
+                        (float) b.getWidth() / 2, (float) b.getHeight() / 2);
+            }
+
+            try {
+                Bitmap b2 = Bitmap.createBitmap(
+                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+                if (b != b2) {
+                    b.recycle();
+                    b = b2;
+                }
+            } catch (OutOfMemoryError ex) {
+                // We have no memory to rotate. Return the original bitmap.
+            }
+        }
+        return b;
+    }
+
+    /*
+     * Compute the sample size as a function of minSideLength
+     * and maxNumOfPixels.
+     * minSideLength is used to specify that minimal width or height of a
+     * bitmap.
+     * maxNumOfPixels is used to specify the maximal size in pixels that is
+     * tolerable in terms of memory usage.
+     *
+     * The function returns a sample size based on the constraints.
+     * Both size and minSideLength can be passed in as -1
+     * which indicates no care of the corresponding constraint.
+     * The functions prefers returning a sample size that
+     * generates a smaller bitmap, unless minSideLength = -1.
+     *
+     * Also, the function rounds up the sample size to a power of 2 or multiple
+     * of 8 because BitmapFactory only honors sample size this way.
+     * For example, BitmapFactory downsamples an image by 2 even though the
+     * request is 3. So we round up the sample size to avoid OOM.
+     */
+    public static int computeSampleSize(BitmapFactory.Options options,
+            int minSideLength, int maxNumOfPixels) {
+        int initialSize = computeInitialSampleSize(options, minSideLength,
+                maxNumOfPixels);
+
+        int roundedSize;
+        if (initialSize <= 8) {
+            roundedSize = 1;
+            while (roundedSize < initialSize) {
+                roundedSize <<= 1;
+            }
+        } else {
+            roundedSize = (initialSize + 7) / 8 * 8;
+        }
+
+        return roundedSize;
+    }
+
+    private static int computeInitialSampleSize(BitmapFactory.Options options,
+            int minSideLength, int maxNumOfPixels) {
+        double w = options.outWidth;
+        double h = options.outHeight;
+
+        int lowerBound = (maxNumOfPixels < 0) ? 1 :
+                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
+        int upperBound = (minSideLength < 0) ? 128 :
+                (int) Math.min(Math.floor(w / minSideLength),
+                Math.floor(h / minSideLength));
+
+        if (upperBound < lowerBound) {
+            // return the larger one when there is no overlapping zone.
+            return lowerBound;
+        }
+
+        if (maxNumOfPixels < 0 && minSideLength < 0) {
+            return 1;
+        } else if (minSideLength < 0) {
+            return lowerBound;
+        } else {
+            return upperBound;
+        }
+    }
+
+    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
+        try {
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
+                    options);
+            if (options.mCancel || options.outWidth == -1
+                    || options.outHeight == -1) {
+                return null;
+            }
+            options.inSampleSize = computeSampleSize(
+                    options, -1, maxNumOfPixels);
+            options.inJustDecodeBounds = false;
+
+            options.inDither = false;
+            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
+                    options);
+        } catch (OutOfMemoryError ex) {
+            Log.e(TAG, "Got oom exception ", ex);
+            return null;
+        }
+    }
+
+    public static void closeSilently(Closeable c) {
+        if (c == null) return;
+        try {
+            c.close();
+        } catch (Throwable t) {
+            // do nothing
+        }
+    }
+
+    public static void Assert(boolean cond) {
+        if (!cond) {
+            throw new AssertionError();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private static void throwIfCameraDisabled(Activity activity) throws CameraDisabledException {
+        // Check if device policy has disabled the camera.
+        if (ApiHelper.HAS_GET_CAMERA_DISABLED) {
+            DevicePolicyManager dpm = (DevicePolicyManager) activity.getSystemService(
+                    Context.DEVICE_POLICY_SERVICE);
+            if (dpm.getCameraDisabled(null)) {
+                throw new CameraDisabledException();
+            }
+        }
+    }
+
+    public static CameraManager.CameraProxy openCamera(Activity activity, int cameraId)
+            throws CameraHardwareException, CameraDisabledException {
+        throwIfCameraDisabled(activity);
+
+        try {
+            return CameraHolder.instance().open(cameraId);
+        } catch (CameraHardwareException e) {
+            // In eng build, we throw the exception so that test tool
+            // can detect it and report it
+            if ("eng".equals(Build.TYPE)) {
+                throw new RuntimeException("openCamera failed", e);
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    public static void showErrorAndFinish(final Activity activity, int msgId) {
+        DialogInterface.OnClickListener buttonListener =
+                new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                activity.finish();
+            }
+        };
+        TypedValue out = new TypedValue();
+        activity.getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
+        new AlertDialog.Builder(activity)
+                .setCancelable(false)
+                .setTitle(R.string.camera_error_title)
+                .setMessage(msgId)
+                .setNeutralButton(R.string.dialog_ok, buttonListener)
+                .setIcon(out.resourceId)
+                .show();
+    }
+
+    public static <T> T checkNotNull(T object) {
+        if (object == null) throw new NullPointerException();
+        return object;
+    }
+
+    public static boolean equals(Object a, Object b) {
+        return (a == b) || (a == null ? false : a.equals(b));
+    }
+
+    public static int nextPowerOf2(int n) {
+        n -= 1;
+        n |= n >>> 16;
+        n |= n >>> 8;
+        n |= n >>> 4;
+        n |= n >>> 2;
+        n |= n >>> 1;
+        return n + 1;
+    }
+
+    public static float distance(float x, float y, float sx, float sy) {
+        float dx = x - sx;
+        float dy = y - sy;
+        return FloatMath.sqrt(dx * dx + dy * dy);
+    }
+
+    public static int clamp(int x, int min, int max) {
+        if (x > max) return max;
+        if (x < min) return min;
+        return x;
+    }
+
+    public static int getDisplayRotation(Activity activity) {
+        int rotation = activity.getWindowManager().getDefaultDisplay()
+                .getRotation();
+        switch (rotation) {
+            case Surface.ROTATION_0: return 0;
+            case Surface.ROTATION_90: return 90;
+            case Surface.ROTATION_180: return 180;
+            case Surface.ROTATION_270: return 270;
+        }
+        return 0;
+    }
+
+    public static int getDisplayOrientation(int degrees, int cameraId) {
+        // See android.hardware.Camera.setDisplayOrientation for
+        // documentation.
+        Camera.CameraInfo info = new Camera.CameraInfo();
+        Camera.getCameraInfo(cameraId, info);
+        int result;
+        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+            result = (info.orientation + degrees) % 360;
+            result = (360 - result) % 360;  // compensate the mirror
+        } else {  // back-facing
+            result = (info.orientation - degrees + 360) % 360;
+        }
+        return result;
+    }
+
+    public static int getCameraOrientation(int cameraId) {
+        Camera.CameraInfo info = new Camera.CameraInfo();
+        Camera.getCameraInfo(cameraId, info);
+        return info.orientation;
+    }
+
+    public static int roundOrientation(int orientation, int orientationHistory) {
+        boolean changeOrientation = false;
+        if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
+            changeOrientation = true;
+        } else {
+            int dist = Math.abs(orientation - orientationHistory);
+            dist = Math.min( dist, 360 - dist );
+            changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS );
+        }
+        if (changeOrientation) {
+            return ((orientation + 45) / 90 * 90) % 360;
+        }
+        return orientationHistory;
+    }
+
+    @SuppressWarnings("deprecation")
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+    private static Point getDefaultDisplaySize(Activity activity, Point size) {
+        Display d = activity.getWindowManager().getDefaultDisplay();
+        if (Build.VERSION.SDK_INT >= ApiHelper.VERSION_CODES.HONEYCOMB_MR2) {
+            d.getSize(size);
+        } else {
+            size.set(d.getWidth(), d.getHeight());
+        }
+        return size;
+    }
+
+    public static Size getOptimalPreviewSize(Activity currentActivity,
+            List<Size> sizes, double targetRatio) {
+        // Use a very small tolerance because we want an exact match.
+        final double ASPECT_TOLERANCE = 0.001;
+        if (sizes == null) return null;
+
+        Size optimalSize = null;
+        double minDiff = Double.MAX_VALUE;
+
+        // Because of bugs of overlay and layout, we sometimes will try to
+        // layout the viewfinder in the portrait orientation and thus get the
+        // wrong size of preview surface. When we change the preview size, the
+        // new overlay will be created before the old one closed, which causes
+        // an exception. For now, just get the screen size.
+        Point point = getDefaultDisplaySize(currentActivity, new Point());
+        int targetHeight = Math.min(point.x, point.y);
+        // Try to find an size match aspect ratio and size
+        for (Size size : sizes) {
+            double ratio = (double) size.width / size.height;
+            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+            if (Math.abs(size.height - targetHeight) < minDiff) {
+                optimalSize = size;
+                minDiff = Math.abs(size.height - targetHeight);
+            }
+        }
+        // Cannot find the one match the aspect ratio. This should not happen.
+        // Ignore the requirement.
+        if (optimalSize == null) {
+            Log.w(TAG, "No preview size match the aspect ratio");
+            minDiff = Double.MAX_VALUE;
+            for (Size size : sizes) {
+                if (Math.abs(size.height - targetHeight) < minDiff) {
+                    optimalSize = size;
+                    minDiff = Math.abs(size.height - targetHeight);
+                }
+            }
+        }
+        return optimalSize;
+    }
+
+    // Returns the largest picture size which matches the given aspect ratio.
+    public static Size getOptimalVideoSnapshotPictureSize(
+            List<Size> sizes, double targetRatio) {
+        // Use a very small tolerance because we want an exact match.
+        final double ASPECT_TOLERANCE = 0.001;
+        if (sizes == null) return null;
+
+        Size optimalSize = null;
+
+        // Try to find a size matches aspect ratio and has the largest width
+        for (Size size : sizes) {
+            double ratio = (double) size.width / size.height;
+            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
+            if (optimalSize == null || size.width > optimalSize.width) {
+                optimalSize = size;
+            }
+        }
+
+        // Cannot find one that matches the aspect ratio. This should not happen.
+        // Ignore the requirement.
+        if (optimalSize == null) {
+            Log.w(TAG, "No picture size match the aspect ratio");
+            for (Size size : sizes) {
+                if (optimalSize == null || size.width > optimalSize.width) {
+                    optimalSize = size;
+                }
+            }
+        }
+        return optimalSize;
+    }
+
+    public static void dumpParameters(Parameters parameters) {
+        String flattened = parameters.flatten();
+        StringTokenizer tokenizer = new StringTokenizer(flattened, ";");
+        Log.d(TAG, "Dump all camera parameters:");
+        while (tokenizer.hasMoreElements()) {
+            Log.d(TAG, tokenizer.nextToken());
+        }
+    }
+
+    /**
+     * Returns whether the device is voice-capable (meaning, it can do MMS).
+     */
+    public static boolean isMmsCapable(Context context) {
+        TelephonyManager telephonyManager = (TelephonyManager)
+                context.getSystemService(Context.TELEPHONY_SERVICE);
+        if (telephonyManager == null) {
+            return false;
+        }
+
+        try {
+            Class<?> partypes[] = new Class[0];
+            Method sIsVoiceCapable = TelephonyManager.class.getMethod(
+                    "isVoiceCapable", partypes);
+
+            Object arglist[] = new Object[0];
+            Object retobj = sIsVoiceCapable.invoke(telephonyManager, arglist);
+            return (Boolean) retobj;
+        } catch (java.lang.reflect.InvocationTargetException ite) {
+            // Failure, must be another device.
+            // Assume that it is voice capable.
+        } catch (IllegalAccessException iae) {
+            // Failure, must be an other device.
+            // Assume that it is voice capable.
+        } catch (NoSuchMethodException nsme) {
+        }
+        return true;
+    }
+
+    // This is for test only. Allow the camera to launch the specific camera.
+    public static int getCameraFacingIntentExtras(Activity currentActivity) {
+        int cameraId = -1;
+
+        int intentCameraId =
+                currentActivity.getIntent().getIntExtra(Util.EXTRAS_CAMERA_FACING, -1);
+
+        if (isFrontCameraIntent(intentCameraId)) {
+            // Check if the front camera exist
+            int frontCameraId = CameraHolder.instance().getFrontCameraId();
+            if (frontCameraId != -1) {
+                cameraId = frontCameraId;
+            }
+        } else if (isBackCameraIntent(intentCameraId)) {
+            // Check if the back camera exist
+            int backCameraId = CameraHolder.instance().getBackCameraId();
+            if (backCameraId != -1) {
+                cameraId = backCameraId;
+            }
+        }
+        return cameraId;
+    }
+
+    private static boolean isFrontCameraIntent(int intentCameraId) {
+        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
+    }
+
+    private static boolean isBackCameraIntent(int intentCameraId) {
+        return (intentCameraId == android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
+    }
+
+    private static int sLocation[] = new int[2];
+
+    // This method is not thread-safe.
+    public static boolean pointInView(float x, float y, View v) {
+        v.getLocationInWindow(sLocation);
+        return x >= sLocation[0] && x < (sLocation[0] + v.getWidth())
+                && y >= sLocation[1] && y < (sLocation[1] + v.getHeight());
+    }
+
+    public static int[] getRelativeLocation(View reference, View view) {
+        reference.getLocationInWindow(sLocation);
+        int referenceX = sLocation[0];
+        int referenceY = sLocation[1];
+        view.getLocationInWindow(sLocation);
+        sLocation[0] -= referenceX;
+        sLocation[1] -= referenceY;
+        return sLocation;
+    }
+
+    public static boolean isUriValid(Uri uri, ContentResolver resolver) {
+        if (uri == null) return false;
+
+        try {
+            ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
+            if (pfd == null) {
+                Log.e(TAG, "Fail to open URI. URI=" + uri);
+                return false;
+            }
+            pfd.close();
+        } catch (IOException ex) {
+            return false;
+        }
+        return true;
+    }
+
+    public static void viewUri(Uri uri, Context context) {
+        if (!isUriValid(uri, context.getContentResolver())) {
+            Log.e(TAG, "Uri invalid. uri=" + uri);
+            return;
+        }
+
+        try {
+            context.startActivity(new Intent(Util.REVIEW_ACTION, uri));
+        } catch (ActivityNotFoundException ex) {
+            try {
+                context.startActivity(new Intent(Intent.ACTION_VIEW, uri));
+            } catch (ActivityNotFoundException e) {
+                Log.e(TAG, "review image fail. uri=" + uri, e);
+            }
+        }
+    }
+
+    public static void dumpRect(RectF rect, String msg) {
+        Log.v(TAG, msg + "=(" + rect.left + "," + rect.top
+                + "," + rect.right + "," + rect.bottom + ")");
+    }
+
+    public static void rectFToRect(RectF rectF, Rect rect) {
+        rect.left = Math.round(rectF.left);
+        rect.top = Math.round(rectF.top);
+        rect.right = Math.round(rectF.right);
+        rect.bottom = Math.round(rectF.bottom);
+    }
+
+    public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
+            int viewWidth, int viewHeight) {
+        // Need mirror for front camera.
+        matrix.setScale(mirror ? -1 : 1, 1);
+        // This is the value for android.hardware.Camera.setDisplayOrientation.
+        matrix.postRotate(displayOrientation);
+        // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
+        // UI coordinates range from (0, 0) to (width, height).
+        matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
+        matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
+    }
+
+    public static String createJpegName(long dateTaken) {
+        synchronized (sImageFileNamer) {
+            return sImageFileNamer.generateName(dateTaken);
+        }
+    }
+
+    public static void broadcastNewPicture(Context context, Uri uri) {
+        context.sendBroadcast(new Intent(ACTION_NEW_PICTURE, uri));
+        // Keep compatibility
+        context.sendBroadcast(new Intent("com.android.camera.NEW_PICTURE", uri));
+    }
+
+    public static void fadeIn(View view, float startAlpha, float endAlpha, long duration) {
+        if (view.getVisibility() == View.VISIBLE) return;
+
+        view.setVisibility(View.VISIBLE);
+        Animation animation = new AlphaAnimation(startAlpha, endAlpha);
+        animation.setDuration(duration);
+        view.startAnimation(animation);
+    }
+
+    public static void fadeIn(View view) {
+        fadeIn(view, 0F, 1F, 400);
+
+        // We disabled the button in fadeOut(), so enable it here.
+        view.setEnabled(true);
+    }
+
+    public static void fadeOut(View view) {
+        if (view.getVisibility() != View.VISIBLE) return;
+
+        // Since the button is still clickable before fade-out animation
+        // ends, we disable the button first to block click.
+        view.setEnabled(false);
+        Animation animation = new AlphaAnimation(1F, 0F);
+        animation.setDuration(400);
+        view.startAnimation(animation);
+        view.setVisibility(View.GONE);
+    }
+
+    public static int getJpegRotation(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];
+            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+                rotation = (info.orientation - orientation + 360) % 360;
+            } else {  // back-facing camera
+                rotation = (info.orientation + orientation) % 360;
+            }
+        }
+        return rotation;
+    }
+
+    public static void setGpsParameters(Parameters parameters, Location loc) {
+        // Clear previous GPS location from the parameters.
+        parameters.removeGpsData();
+
+        // We always encode GpsTimeStamp
+        parameters.setGpsTimestamp(System.currentTimeMillis() / 1000);
+
+        // Set GPS location.
+        if (loc != null) {
+            double lat = loc.getLatitude();
+            double lon = loc.getLongitude();
+            boolean hasLatLon = (lat != 0.0d) || (lon != 0.0d);
+
+            if (hasLatLon) {
+                Log.d(TAG, "Set gps location");
+                parameters.setGpsLatitude(lat);
+                parameters.setGpsLongitude(lon);
+                parameters.setGpsProcessingMethod(loc.getProvider().toUpperCase());
+                if (loc.hasAltitude()) {
+                    parameters.setGpsAltitude(loc.getAltitude());
+                } else {
+                    // for NETWORK_PROVIDER location provider, we may have
+                    // no altitude information, but the driver needs it, so
+                    // we fake one.
+                    parameters.setGpsAltitude(0);
+                }
+                if (loc.getTime() != 0) {
+                    // Location.getTime() is UTC in milliseconds.
+                    // gps-timestamp is UTC in seconds.
+                    long utcTimeSeconds = loc.getTime() / 1000;
+                    parameters.setGpsTimestamp(utcTimeSeconds);
+                }
+            } else {
+                loc = null;
+            }
+        }
+    }
+
+    private static class ImageFileNamer {
+        private SimpleDateFormat mFormat;
+
+        // The date (in milliseconds) used to generate the last name.
+        private long mLastDate;
+
+        // Number of names generated for the same second.
+        private int mSameSecondCount;
+
+        public ImageFileNamer(String format) {
+            mFormat = new SimpleDateFormat(format);
+        }
+
+        public String generateName(long dateTaken) {
+            Date date = new Date(dateTaken);
+            String result = mFormat.format(date);
+
+            // If the last name was generated for the same second,
+            // we append _1, _2, etc to the name.
+            if (dateTaken / 1000 == mLastDate / 1000) {
+                mSameSecondCount++;
+                result += "_" + mSameSecondCount;
+            } else {
+                mLastDate = dateTaken;
+                mSameSecondCount = 0;
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/src/com/android/camera/VideoController.java b/src/com/android/camera/VideoController.java
new file mode 100644
index 0000000..474f521
--- /dev/null
+++ b/src/com/android/camera/VideoController.java
@@ -0,0 +1,36 @@
+/*
+ * 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.view.View;
+
+import com.android.camera.ShutterButton.OnShutterButtonListener;
+
+public interface VideoController extends OnShutterButtonListener {
+
+    public void onReviewDoneClicked(View view);
+    public void onReviewCancelClicked(View viwe);
+    public void onReviewPlayClicked(View view);
+
+    public boolean isVideoCaptureIntent();
+    public boolean isInReviewMode();
+    public int onZoomChanged(int index);
+
+    public void onSingleTapUp(View view, int x, int y);
+
+    public void stopPreview();
+}
diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java
new file mode 100644
index 0000000..9bfcdea
--- /dev/null
+++ b/src/com/android/camera/VideoMenu.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2012 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.Context;
+import android.view.LayoutInflater;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.MoreSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.TimeIntervalPopup;
+import com.android.gallery3d.R;
+
+public class VideoMenu extends PieController
+        implements MoreSettingPopup.Listener,
+        ListPrefSettingPopup.Listener,
+        TimeIntervalPopup.Listener {
+
+    private static String TAG = "CAM_VideoMenu";
+
+    private VideoUI mUI;
+    private String[] mOtherKeys;
+    private AbstractSettingPopup mPopup;
+
+    private static final int POPUP_NONE = 0;
+    private static final int POPUP_FIRST_LEVEL = 1;
+    private static final int POPUP_SECOND_LEVEL = 2;
+    private int mPopupStatus;
+    private CameraActivity mActivity;
+
+    public VideoMenu(CameraActivity activity, VideoUI ui, PieRenderer pie) {
+        super(activity, pie);
+        mUI = ui;
+        mActivity = activity;
+    }
+
+    public void initialize(PreferenceGroup group) {
+        super.initialize(group);
+        mPopup = null;
+        mPopupStatus = POPUP_NONE;
+        PieItem item = null;
+        // white balance
+        if (group.findPreference(CameraSettings.KEY_WHITE_BALANCE) != null) {
+            item = makeItem(CameraSettings.KEY_WHITE_BALANCE);
+            mRenderer.addItem(item);
+        }
+        // settings popup
+        mOtherKeys = new String[] {
+                CameraSettings.KEY_VIDEO_EFFECT,
+                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+                CameraSettings.KEY_VIDEO_QUALITY,
+                CameraSettings.KEY_RECORD_LOCATION
+        };
+        item = makeItem(R.drawable.ic_settings_holo_light);
+        item.setLabel(mActivity.getResources().getString(R.string.camera_menu_settings_label));
+        item.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(PieItem item) {
+                if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+                    initializePopup();
+                    mPopupStatus = POPUP_FIRST_LEVEL;
+                }
+                mUI.showPopup(mPopup);
+            }
+        });
+        mRenderer.addItem(item);
+        // camera switcher
+        if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
+            item = makeItem(R.drawable.ic_switch_back);
+            IconListPreference lpref = (IconListPreference) group.findPreference(
+                    CameraSettings.KEY_CAMERA_ID);
+            item.setLabel(lpref.getLabel());
+            item.setImageResource(mActivity,
+                    ((IconListPreference) lpref).getIconIds()
+                    [lpref.findIndexOfValue(lpref.getValue())]);
+
+            final PieItem fitem = item;
+            item.setOnClickListener(new OnClickListener() {
+
+                @Override
+                public void onClick(PieItem item) {
+                    // Find the index of next camera.
+                    ListPreference pref =
+                            mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+                    if (pref != null) {
+                        int index = pref.findIndexOfValue(pref.getValue());
+                        CharSequence[] values = pref.getEntryValues();
+                        index = (index + 1) % values.length;
+                        int newCameraId = Integer.parseInt((String) values[index]);
+                        fitem.setImageResource(mActivity,
+                                ((IconListPreference) pref).getIconIds()[index]);
+                        fitem.setLabel(pref.getLabel());
+                        mListener.onCameraPickerClicked(newCameraId);
+                    }
+                }
+            });
+            mRenderer.addItem(item);
+        }
+        // flash
+        if (group.findPreference(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE) != null) {
+            item = makeItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE);
+            mRenderer.addItem(item);
+        }
+    }
+
+    @Override
+    public void reloadPreferences() {
+        super.reloadPreferences();
+        if (mPopup != null) {
+            mPopup.reloadPreference();
+        }
+    }
+
+    @Override
+    public void overrideSettings(final String ... keyvalues) {
+        super.overrideSettings(keyvalues);
+        if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+            mPopupStatus = POPUP_FIRST_LEVEL;
+            initializePopup();
+        }
+        ((MoreSettingPopup) mPopup).overrideSettings(keyvalues);
+    }
+
+    @Override
+    // Hit when an item in the second-level popup gets selected
+    public void onListPrefChanged(ListPreference pref) {
+        if (mPopup != null) {
+            if (mPopupStatus == POPUP_SECOND_LEVEL) {
+                mUI.dismissPopup(true);
+            }
+        }
+        super.onSettingChanged(pref);
+    }
+
+    protected void initializePopup() {
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        MoreSettingPopup popup = (MoreSettingPopup) inflater.inflate(
+                R.layout.more_setting_popup, null, false);
+        popup.setSettingChangedListener(this);
+        popup.initialize(mPreferenceGroup, mOtherKeys);
+        if (mActivity.isSecureCamera()) {
+            // Prevent location preference from getting changed in secure camera mode
+            popup.setPreferenceEnabled(CameraSettings.KEY_RECORD_LOCATION, false);
+        }
+        mPopup = popup;
+    }
+
+    public void popupDismissed(boolean topPopupOnly) {
+        // if the 2nd level popup gets dismissed
+        if (mPopupStatus == POPUP_SECOND_LEVEL) {
+            initializePopup();
+            mPopupStatus = POPUP_FIRST_LEVEL;
+            if (topPopupOnly) mUI.showPopup(mPopup);
+        }
+    }
+
+    @Override
+    // Hit when an item in the first-level popup gets selected, then bring up
+    // the second-level popup
+    public void onPreferenceClicked(ListPreference pref) {
+        if (mPopupStatus != POPUP_FIRST_LEVEL) return;
+
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        if (CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL.equals(pref.getKey())) {
+            TimeIntervalPopup timeInterval = (TimeIntervalPopup) inflater.inflate(
+                    R.layout.time_interval_popup, null, false);
+            timeInterval.initialize((IconListPreference) pref);
+            timeInterval.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mPopup = timeInterval;
+        } else {
+            ListPrefSettingPopup basic = (ListPrefSettingPopup) inflater.inflate(
+                    R.layout.list_pref_setting_popup, null, false);
+            basic.initialize(pref);
+            basic.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mPopup = basic;
+        }
+        mUI.showPopup(mPopup);
+        mPopupStatus = POPUP_SECOND_LEVEL;
+    }
+
+}
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
new file mode 100644
index 0000000..2c654fc
--- /dev/null
+++ b/src/com/android/camera/VideoModule.java
@@ -0,0 +1,2319 @@
+/*
+ * Copyright (C) 2012 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.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.media.CamcorderProfile;
+import android.media.CameraProfile;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.util.AccessibilityUtils;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+public class VideoModule implements CameraModule,
+    VideoController,
+    CameraPreference.OnPreferenceChangedListener,
+    ShutterButton.OnShutterButtonListener,
+    MediaRecorder.OnErrorListener,
+    MediaRecorder.OnInfoListener,
+    EffectsRecorder.EffectsListener {
+
+    private static final String TAG = "CAM_VideoModule";
+
+    // We number the request code from 1000 to avoid collision with Gallery.
+    private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
+
+    private static final int CHECK_DISPLAY_ROTATION = 3;
+    private static final int CLEAR_SCREEN_DELAY = 4;
+    private static final int UPDATE_RECORD_TIME = 5;
+    private static final int ENABLE_SHUTTER_BUTTON = 6;
+    private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
+    private static final int SWITCH_CAMERA = 8;
+    private static final int SWITCH_CAMERA_START_ANIMATION = 9;
+    private static final int HIDE_SURFACE_VIEW = 10;
+    private static final int CAPTURE_ANIMATION_DONE = 11;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
+
+    /**
+     * An unpublished intent flag requesting to start recording straight away
+     * and return as soon as recording is stopped.
+     * TODO: consider publishing by moving into MediaStore.
+     */
+    private static final String EXTRA_QUICK_CAPTURE =
+            "android.intent.extra.quickCapture";
+
+    private static final int MIN_THUMB_SIZE = 64;
+    // module fields
+    private CameraActivity mActivity;
+    private boolean mPaused;
+    private int mCameraId;
+    private Parameters mParameters;
+
+    private Boolean mCameraOpened = false;
+
+    private boolean mSnapshotInProgress = false;
+
+    private static final String EFFECT_BG_FROM_GALLERY = "gallery";
+
+    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+    private ComboPreferences mPreferences;
+    private PreferenceGroup mPreferenceGroup;
+
+    private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener;
+
+    private boolean mIsVideoCaptureIntent;
+    private boolean mQuickCapture;
+    private boolean mIsInReviewMode = false;
+
+    private MediaRecorder mMediaRecorder;
+    private EffectsRecorder mEffectsRecorder;
+    private boolean mEffectsDisplayResult;
+
+    private int mEffectType = EffectsRecorder.EFFECT_NONE;
+    private Object mEffectParameter = null;
+    private String mEffectUriFromGallery = null;
+    private String mPrefVideoEffectDefault;
+    private boolean mResetEffect = true;
+
+    private boolean mSwitchingCamera;
+    private boolean mMediaRecorderRecording = false;
+    private long mRecordingStartTime;
+    private boolean mRecordingTimeCountsDown = false;
+    private long mOnResumeTime;
+    // The video file that the hardware camera is about to record into
+    // (or is recording into.)
+    private String mVideoFilename;
+    private ParcelFileDescriptor mVideoFileDescriptor;
+
+    // The video file that has already been recorded, and that is being
+    // examined by the user.
+    private String mCurrentVideoFilename;
+    private Uri mCurrentVideoUri;
+    private ContentValues mCurrentVideoValues;
+
+    private CamcorderProfile mProfile;
+
+    // The video duration limit. 0 menas no limit.
+    private int mMaxVideoDurationInMs;
+
+    // Time Lapse parameters.
+    private boolean mCaptureTimeLapse = false;
+    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
+    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
+
+    boolean mPreviewing = false; // True if preview is started.
+    // The display rotation in degrees. This is only valid when mPreviewing is
+    // true.
+    private int mDisplayRotation;
+    private int mCameraDisplayOrientation;
+
+    private int mDesiredPreviewWidth;
+    private int mDesiredPreviewHeight;
+    private ContentResolver mContentResolver;
+
+    private LocationManager mLocationManager;
+
+    private int mPendingSwitchCameraId;
+
+    private final Handler mHandler = new MainHandler();
+    private VideoUI mUI;
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+    private int mZoomValue;  // The current zoom value.
+
+    private boolean mRestoreFlash;  // This is used to check if we need to restore the flash
+                                    // status when going back from gallery.
+
+    private final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener =
+            new MediaSaveService.OnMediaSavedListener() {
+                @Override
+                public void onMediaSaved(Uri uri) {
+                    if (uri != null) {
+                        mActivity.addSecureAlbumItemIfNeeded(true, uri);
+                        mActivity.sendBroadcast(
+                                new Intent(Util.ACTION_NEW_VIDEO, uri));
+                        Util.broadcastNewPicture(mActivity, uri);
+                    }
+                }
+            };
+
+    private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener =
+            new MediaSaveService.OnMediaSavedListener() {
+                @Override
+                public void onMediaSaved(Uri uri) {
+                    if (uri != null) {
+                        Util.broadcastNewPicture(mActivity, uri);
+                    }
+                }
+            };
+
+
+    protected class CameraOpenThread extends Thread {
+        @Override
+        public void run() {
+            openCamera();
+        }
+    }
+
+    private void openCamera() {
+        try {
+            synchronized(mCameraOpened) {
+                if (!mCameraOpened) {
+                    mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId);
+                    mCameraOpened = true;
+                }
+            }
+            mParameters = mActivity.mCameraDevice.getParameters();
+        } catch (CameraHardwareException e) {
+            mActivity.mOpenCameraFail = true;
+        } catch (CameraDisabledException e) {
+            mActivity.mCameraDisabled = true;
+        }
+    }
+
+    // This Handler is used to post message back onto the main thread of the
+    // application
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                case ENABLE_SHUTTER_BUTTON:
+                    mUI.enableShutter(true);
+                    break;
+
+                case CLEAR_SCREEN_DELAY: {
+                    mActivity.getWindow().clearFlags(
+                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+
+                case UPDATE_RECORD_TIME: {
+                    updateRecordingTime();
+                    break;
+                }
+
+                case CHECK_DISPLAY_ROTATION: {
+                    // Restart the preview if display rotation has changed.
+                    // Sometimes this happens when the device is held upside
+                    // down and camera app is opened. Rotation animation will
+                    // take some time and the rotation value we have got may be
+                    // wrong. Framework does not have a callback for this now.
+                    if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
+                            && !mMediaRecorderRecording && !mSwitchingCamera) {
+                        startPreview();
+                    }
+                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+                    }
+                    break;
+                }
+
+                case SHOW_TAP_TO_SNAPSHOT_TOAST: {
+                    showTapToSnapshotToast();
+                    break;
+                }
+
+                case SWITCH_CAMERA: {
+                    switchCamera();
+                    break;
+                }
+
+                case SWITCH_CAMERA_START_ANIMATION: {
+                    ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+
+                    // Enable all camera controls.
+                    mSwitchingCamera = false;
+                    break;
+                }
+
+                case HIDE_SURFACE_VIEW: {
+                    mUI.hideSurfaceView();
+                    break;
+                }
+
+                case CAPTURE_ANIMATION_DONE: {
+                    mUI.enablePreviewThumb(false);
+                    break;
+                }
+
+                default:
+                    Log.v(TAG, "Unhandled message: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    private BroadcastReceiver mReceiver = null;
+
+    private class MyBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                stopVideoRecording();
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                Toast.makeText(mActivity,
+                        mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+
+    private String createName(long dateTaken) {
+        Date date = new Date(dateTaken);
+        SimpleDateFormat dateFormat = new SimpleDateFormat(
+                mActivity.getString(R.string.video_file_name_format));
+
+        return dateFormat.format(date);
+    }
+
+    private int getPreferredCameraId(ComboPreferences preferences) {
+        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+        if (intentCameraId != -1) {
+            // Testing purpose. Launch a specific camera through the intent
+            // extras.
+            return intentCameraId;
+        } else {
+            return CameraSettings.readPreferredCameraId(preferences);
+        }
+    }
+
+    private void initializeSurfaceView() {
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {  // API level < 16
+                mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() {
+                    @Override
+                    public void onFrameDrawn(CameraScreenNail c) {
+                        mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW);
+                    }
+                };
+            mUI.getSurfaceHolder().addCallback(mUI);
+        }
+    }
+
+    @Override
+    public void init(CameraActivity activity, View root, boolean reuseScreenNail) {
+        mActivity = activity;
+        mUI = new VideoUI(activity, this, root);
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mCameraId = getPreferredCameraId(mPreferences);
+
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+
+        mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras();
+        mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
+        resetEffect();
+
+        /*
+         * 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();
+
+        mContentResolver = mActivity.getContentResolver();
+
+        // Surface texture is from camera screen nail and startPreview needs it.
+        // This must be done before startPreview.
+        mIsVideoCaptureIntent = isVideoCaptureIntent();
+        if (reuseScreenNail) {
+            mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent);
+        } else {
+            mActivity.createCameraScreenNail(!mIsVideoCaptureIntent);
+        }
+        initializeSurfaceView();
+
+        // Make sure camera device is opened.
+        try {
+            cameraOpenThread.join();
+            if (mActivity.mOpenCameraFail) {
+                Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+                return;
+            } else if (mActivity.mCameraDisabled) {
+                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                return;
+            }
+        } catch (InterruptedException ex) {
+            // ignore
+        }
+
+        readVideoPreferences();
+        mUI.setPrefChangedListener(this);
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                startPreview();
+            }
+        }).start();
+
+        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+        mLocationManager = new LocationManager(mActivity, null);
+
+        mUI.setOrientationIndicator(0, false);
+        setDisplayOrientation();
+
+        mUI.showTimeLapseUI(mCaptureTimeLapse);
+        initializeVideoSnapshot();
+        resizeForPreviewAspectRatio();
+
+        initializeVideoControl();
+        mPendingSwitchCameraId = -1;
+        mUI.updateOnScreenIndicators(mParameters, mPreferences);
+
+        // Disable the shutter button if effects are ON since it might take
+        // a little more time for the effects preview to be ready. We do not
+        // want to allow recording before that happens. The shutter button
+        // will be enabled when we get the message from effectsrecorder that
+        // the preview is running. This becomes critical when the camera is
+        // swapped.
+        if (effectsActive()) {
+            mUI.enableShutter(false);
+        }
+    }
+
+    // SingleTapListener
+    // Preview area is touched. Take a picture.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        if (mMediaRecorderRecording && effectsActive()) {
+            new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
+                    mOrientation).show();
+            return;
+        }
+
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) {
+            return;
+        }
+
+        if (!mMediaRecorderRecording) {
+            // check for dismissing popup
+            mUI.dismissPopup(true);
+            return;
+        }
+
+        // Set rotation and gps data.
+        int rotation = Util.getJpegRotation(mCameraId, mOrientation);
+        mParameters.setRotation(rotation);
+        Location loc = mLocationManager.getCurrentLocation();
+        Util.setGpsParameters(mParameters, loc);
+        mActivity.mCameraDevice.setParameters(mParameters);
+
+        Log.v(TAG, "Video snapshot start");
+        mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
+        showVideoSnapshotUI(true);
+        mSnapshotInProgress = true;
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
+    }
+
+    @Override
+    public void onStop() {}
+
+    private void loadCameraPreferences() {
+        CameraSettings settings = new CameraSettings(mActivity, mParameters,
+                mCameraId, CameraHolder.instance().getCameraInfo());
+        // Remove the video quality preference setting when the quality is given in the intent.
+        mPreferenceGroup = filterPreferenceScreenByIntent(
+                settings.getPreferenceGroup(R.xml.video_preferences));
+    }
+
+    private void initializeVideoControl() {
+        loadCameraPreferences();
+        mUI.initializePopup(mPreferenceGroup);
+        if (effectsActive()) {
+            mUI.overrideSettings(
+                    CameraSettings.KEY_VIDEO_QUALITY,
+                    Integer.toString(getLowVideoQuality()));
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private static int getLowVideoQuality() {
+        if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
+            return CamcorderProfile.QUALITY_480P;
+        } else {
+            return CamcorderProfile.QUALITY_LOW;
+        }
+    }
+
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+        // We keep the last known orientation. So if the user first orient
+        // the camera then point the camera to floor or sky, we still have
+        // the correct orientation.
+        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+        int newOrientation = Util.roundOrientation(orientation, mOrientation);
+
+        if (mOrientation != newOrientation) {
+            mOrientation = newOrientation;
+            // The input of effects recorder is affected by
+            // android.hardware.Camera.setDisplayOrientation. Its value only
+            // compensates the camera orientation (no Display.getRotation).
+            // So the orientation hint here should only consider sensor
+            // orientation.
+            if (effectsActive()) {
+                mEffectsRecorder.setOrientationHint(mOrientation);
+            }
+        }
+
+        // Show the toast after getting the first orientation changed.
+        if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
+            mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
+            showTapToSnapshotToast();
+        }
+    }
+
+    private void startPlayVideoActivity() {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
+        try {
+            mActivity.startActivity(intent);
+        } catch (ActivityNotFoundException ex) {
+            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
+        }
+    }
+
+    @OnClickAttr
+    public void onReviewPlayClicked(View v) {
+        startPlayVideoActivity();
+    }
+
+    @OnClickAttr
+    public void onReviewDoneClicked(View v) {
+        mIsInReviewMode = false;
+        doReturnToCaller(true);
+    }
+
+    @OnClickAttr
+    public void onReviewCancelClicked(View v) {
+        mIsInReviewMode = false;
+        stopVideoRecording();
+        doReturnToCaller(false);
+    }
+
+    @Override
+    public boolean isInReviewMode() {
+        return mIsInReviewMode;
+    }
+
+    private void onStopVideoRecording() {
+        mEffectsDisplayResult = true;
+        boolean recordFail = stopVideoRecording();
+        if (mIsVideoCaptureIntent) {
+            if (!effectsActive()) {
+                if (mQuickCapture) {
+                    doReturnToCaller(!recordFail);
+                } else if (!recordFail) {
+                    showCaptureResult();
+                }
+            }
+        } else if (!recordFail){
+            // Start capture animation.
+            if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+                // The capture animation is disabled on ICS because we use SurfaceView
+                // for preview during recording. When the recording is done, we switch
+                // back to use SurfaceTexture for preview and we need to stop then start
+                // the preview. This will cause the preview flicker since the preview
+                // will not be continuous for a short period of time.
+                ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+
+                mUI.enablePreviewThumb(true);
+
+                // Make sure to disable the thumbnail preview after the
+                // animation is done to disable the click target.
+                mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
+                mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
+                        CaptureAnimManager.getAnimationDuration());
+            }
+        }
+    }
+
+    public void onProtectiveCurtainClick(View v) {
+        // Consume clicks
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        if (mUI.collapseCameraControls() || mSwitchingCamera) return;
+
+        boolean stop = mMediaRecorderRecording;
+
+        if (stop) {
+            onStopVideoRecording();
+        } else {
+            startVideoRecording();
+        }
+        mUI.enableShutter(false);
+
+        // Keep the shutter button disabled when in video capture intent
+        // mode and recording is stopped. It'll be re-enabled when
+        // re-take button is clicked.
+        if (!(mIsVideoCaptureIntent && stop)) {
+            mHandler.sendEmptyMessageDelayed(
+                    ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
+        }
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+        mUI.setShutterPressed(pressed);
+    }
+
+    private void readVideoPreferences() {
+        // The preference stores values from ListPreference and is thus string type for all values.
+        // We need to convert it to int manually.
+        String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
+                mActivity.getResources().getString(R.string.pref_video_quality_default));
+        String videoQuality =
+                mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
+                        defaultQuality);
+        int quality = Integer.valueOf(videoQuality);
+
+        // Set video quality.
+        Intent intent = mActivity.getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+            int extraVideoQuality =
+                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
+            if (extraVideoQuality > 0) {
+                quality = CamcorderProfile.QUALITY_HIGH;
+            } else {  // 0 is mms.
+                quality = CamcorderProfile.QUALITY_LOW;
+            }
+        }
+
+        // Set video duration limit. The limit is read from the preference,
+        // unless it is specified in the intent.
+        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+            int seconds =
+                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
+            mMaxVideoDurationInMs = 1000 * seconds;
+        } else {
+            mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
+        }
+
+        // Set effect
+        mEffectType = CameraSettings.readEffectType(mPreferences);
+        if (mEffectType != EffectsRecorder.EFFECT_NONE) {
+            mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+            // Set quality to be no higher than 480p.
+            CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
+            if (profile.videoFrameHeight > 480) {
+                quality = getLowVideoQuality();
+            }
+        } else {
+            mEffectParameter = null;
+        }
+        // Read time lapse recording interval.
+        if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
+            String frameIntervalStr = mPreferences.getString(
+                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+                    mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
+            mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
+            mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
+        }
+        // TODO: This should be checked instead directly +1000.
+        if (mCaptureTimeLapse) quality += 1000;
+        mProfile = CamcorderProfile.get(mCameraId, quality);
+        getDesiredPreviewSize();
+    }
+
+    private void writeDefaultEffectToPrefs()  {
+        ComboPreferences.Editor editor = mPreferences.edit();
+        editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
+                mActivity.getString(R.string.pref_video_effect_default));
+        editor.apply();
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private void getDesiredPreviewSize() {
+        mParameters = mActivity.mCameraDevice.getParameters();
+        if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
+            if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
+                mDesiredPreviewWidth = mProfile.videoFrameWidth;
+                mDesiredPreviewHeight = mProfile.videoFrameHeight;
+            } else {  // Driver supports separates outputs for preview and video.
+                List<Size> sizes = mParameters.getSupportedPreviewSizes();
+                Size preferred = mParameters.getPreferredPreviewSizeForVideo();
+                int product = preferred.width * preferred.height;
+                Iterator<Size> it = sizes.iterator();
+                // Remove the preview sizes that are not preferred.
+                while (it.hasNext()) {
+                    Size size = it.next();
+                    if (size.width * size.height > product) {
+                        it.remove();
+                    }
+                }
+                Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+                        (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+                mDesiredPreviewWidth = optimalSize.width;
+                mDesiredPreviewHeight = optimalSize.height;
+            }
+        } else {
+            mDesiredPreviewWidth = mProfile.videoFrameWidth;
+            mDesiredPreviewHeight = mProfile.videoFrameHeight;
+        }
+        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
+                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
+    }
+
+    private void resizeForPreviewAspectRatio() {
+        mUI.setAspectRatio(
+                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+    }
+
+    @Override
+    public void installIntentFilter() {
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter =
+                new IntentFilter(Intent.ACTION_MEDIA_EJECT);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addDataScheme("file");
+        mReceiver = new MyBroadcastReceiver();
+        mActivity.registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled)
+            return;
+        mUI.enableShutter(false);
+        mZoomValue = 0;
+
+        showVideoSnapshotUI(false);
+
+        if (!mPreviewing) {
+            resetEffect();
+            openCamera();
+            if (mActivity.mOpenCameraFail) {
+                Util.showErrorAndFinish(mActivity,
+                        R.string.cannot_connect_camera);
+                return;
+            } else if (mActivity.mCameraDisabled) {
+                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                return;
+            }
+            readVideoPreferences();
+            resizeForPreviewAspectRatio();
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    startPreview();
+                }
+            }).start();
+        } else {
+            // preview already started
+            mUI.enableShutter(true);
+        }
+
+        // Initializing it here after the preview is started.
+        mUI.initializeZoom(mParameters);
+
+        keepScreenOnAwhile();
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(mPreferences,
+                mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        if (mPreviewing) {
+            mOnResumeTime = SystemClock.uptimeMillis();
+            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+        }
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_CAMERA, "VideoModule");
+    }
+
+    private void setDisplayOrientation() {
+        mDisplayRotation = Util.getDisplayRotation(mActivity);
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            // The display rotation is handled by gallery.
+            mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId);
+        } else {
+            // We need to consider display rotation ourselves.
+            mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+        }
+        // GLRoot also uses the DisplayRotation, and needs to be told to layout to update
+        mActivity.getGLRoot().requestLayoutContentPane();
+    }
+
+    @Override
+    public int onZoomChanged(int index) {
+        // Not useful to change zoom value when the activity is paused.
+        if (mPaused) return index;
+        mZoomValue = index;
+        if (mParameters == null || mActivity.mCameraDevice == null) return index;
+        // Set zoom parameters asynchronously
+        mParameters.setZoom(mZoomValue);
+        mActivity.mCameraDevice.setParameters(mParameters);
+        Parameters p = mActivity.mCameraDevice.getParameters();
+        if (p != null) return p.getZoom();
+        return index;
+    }
+    private void startPreview() {
+        Log.v(TAG, "startPreview");
+
+        mActivity.mCameraDevice.setErrorCallback(mErrorCallback);
+        if (mPreviewing == true) {
+            stopPreview();
+            if (effectsActive() && mEffectsRecorder != null) {
+                mEffectsRecorder.release();
+                mEffectsRecorder = null;
+            }
+        }
+
+        setDisplayOrientation();
+        mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+        setCameraParameters();
+
+        try {
+            if (!effectsActive()) {
+                if (ApiHelper.HAS_SURFACE_TEXTURE) {
+                    SurfaceTexture surfaceTexture = ((CameraScreenNail) mActivity.mCameraScreenNail)
+                            .getSurfaceTexture();
+                    if (surfaceTexture == null) {
+                        return; // The texture has been destroyed (pause, etc)
+                    }
+                    mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture);
+                } else {
+                    mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
+                }
+                mActivity.mCameraDevice.startPreviewAsync();
+                mPreviewing = true;
+                onPreviewStarted();
+            } else {
+                initializeEffectsPreview();
+                mEffectsRecorder.startPreview();
+                mPreviewing = true;
+                onPreviewStarted();
+            }
+        } catch (Throwable ex) {
+            closeCamera();
+            throw new RuntimeException("startPreview failed", ex);
+        } finally {
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mActivity.mOpenCameraFail) {
+                        Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+                    } else if (mActivity.mCameraDisabled) {
+                        Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                    }
+                }
+            });
+        }
+
+    }
+
+    private void onPreviewStarted() {
+        mUI.enableShutter(true);
+    }
+
+    @Override
+    public void stopPreview() {
+        mActivity.mCameraDevice.stopPreview();
+        mPreviewing = false;
+    }
+
+    // Closing the effects out. Will shut down the effects graph.
+    private void closeEffects() {
+        Log.v(TAG, "Closing effects");
+        mEffectType = EffectsRecorder.EFFECT_NONE;
+        if (mEffectsRecorder == null) {
+            Log.d(TAG, "Effects are already closed. Nothing to do");
+            return;
+        }
+        // This call can handle the case where the camera is already released
+        // after the recording has been stopped.
+        mEffectsRecorder.release();
+        mEffectsRecorder = null;
+    }
+
+    // By default, we want to close the effects as well with the camera.
+    private void closeCamera() {
+        closeCamera(true);
+    }
+
+    // In certain cases, when the effects are active, we may want to shutdown
+    // only the camera related parts, and handle closing the effects in the
+    // effectsUpdate callback.
+    // For example, in onPause, we want to make the camera available to
+    // outside world immediately, however, want to wait till the effects
+    // callback to shut down the effects. In such a case, we just disconnect
+    // the effects from the camera by calling disconnectCamera. That way
+    // the effects can handle that when shutting down.
+    //
+    // @param closeEffectsAlso - indicates whether we want to close the
+    // effects also along with the camera.
+    private void closeCamera(boolean closeEffectsAlso) {
+        Log.v(TAG, "closeCamera");
+        if (mActivity.mCameraDevice == null) {
+            Log.d(TAG, "already stopped.");
+            return;
+        }
+
+        if (mEffectsRecorder != null) {
+            // Disconnect the camera from effects so that camera is ready to
+            // be released to the outside world.
+            mEffectsRecorder.disconnectCamera();
+        }
+        if (closeEffectsAlso) closeEffects();
+        mActivity.mCameraDevice.setZoomChangeListener(null);
+        mActivity.mCameraDevice.setErrorCallback(null);
+        synchronized(mCameraOpened) {
+            if (mCameraOpened) {
+                CameraHolder.instance().release();
+            }
+            mCameraOpened = false;
+        }
+        mActivity.mCameraDevice = null;
+        mPreviewing = false;
+        mSnapshotInProgress = false;
+    }
+
+    private void releasePreviewResources() {
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+            screenNail.releaseSurfaceTexture();
+            if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+                mHandler.removeMessages(HIDE_SURFACE_VIEW);
+                mUI.hideSurfaceView();
+            }
+        }
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+
+        if (mMediaRecorderRecording) {
+            // Camera will be released in onStopVideoRecording.
+            onStopVideoRecording();
+        } else {
+            closeCamera();
+            if (!effectsActive()) releaseMediaRecorder();
+        }
+        if (effectsActive()) {
+            // If the effects are active, make sure we tell the graph that the
+            // surfacetexture is not valid anymore. Disconnect the graph from
+            // the display. This should be done before releasing the surface
+            // texture.
+            mEffectsRecorder.disconnectDisplay();
+        } else {
+            // Close the file descriptor and clear the video namer only if the
+            // effects are not active. If effects are active, we need to wait
+            // till we get the callback from the Effects that the graph is done
+            // recording. That also needs a change in the stopVideoRecording()
+            // call to not call closeCamera if the effects are active, because
+            // that will close down the effects are well, thus making this if
+            // condition invalid.
+            closeVideoFileDescriptor();
+        }
+
+        releasePreviewResources();
+
+        if (mReceiver != null) {
+            mActivity.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+        resetScreenOn();
+
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+        mHandler.removeMessages(SWITCH_CAMERA);
+        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+        mPendingSwitchCameraId = -1;
+        mSwitchingCamera = false;
+        // Call onPause after stopping video recording. So the camera can be
+        // released as soon as possible.
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
+            keepScreenOnAwhile();
+        }
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        if (mPaused) return true;
+        if (mMediaRecorderRecording) {
+            onStopVideoRecording();
+            return true;
+        } else if (mUI.hidePieRenderer()) {
+            return true;
+        } else {
+            return mUI.removeTopLevelPopup();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Do not handle any key if the activity is paused.
+        if (mPaused) {
+            return true;
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+                if (event.getRepeatCount() == 0) {
+                    mUI.clickShutter();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (event.getRepeatCount() == 0) {
+                    mUI.clickShutter();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_MENU:
+                if (mMediaRecorderRecording) return true;
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+                mUI.pressShutter(false);
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isVideoCaptureIntent() {
+        String action = mActivity.getIntent().getAction();
+        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
+    }
+
+    private void doReturnToCaller(boolean valid) {
+        Intent resultIntent = new Intent();
+        int resultCode;
+        if (valid) {
+            resultCode = Activity.RESULT_OK;
+            resultIntent.setData(mCurrentVideoUri);
+        } else {
+            resultCode = Activity.RESULT_CANCELED;
+        }
+        mActivity.setResultEx(resultCode, resultIntent);
+        mActivity.finish();
+    }
+
+    private void cleanupEmptyFile() {
+        if (mVideoFilename != null) {
+            File f = new File(mVideoFilename);
+            if (f.length() == 0 && f.delete()) {
+                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
+                mVideoFilename = null;
+            }
+        }
+    }
+
+    private void setupMediaRecorderPreviewDisplay() {
+        // Nothing to do here if using SurfaceTexture.
+        if (!ApiHelper.HAS_SURFACE_TEXTURE) {
+            mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
+        } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+            // We stop the preview here before unlocking the device because we
+            // need to change the SurfaceTexture to SurfaceView for preview.
+            stopPreview();
+            mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
+            // The orientation for SurfaceTexture is different from that for
+            // SurfaceView. For SurfaceTexture we don't need to consider the
+            // display rotation. Just consider the sensor's orientation and we
+            // will set the orientation correctly when showing the texture.
+            // Gallery will handle the orientation for the preview. For
+            // SurfaceView we will have to take everything into account so the
+            // display rotation is considered.
+            mActivity.mCameraDevice.setDisplayOrientation(
+                    Util.getDisplayOrientation(mDisplayRotation, mCameraId));
+            mActivity.mCameraDevice.startPreviewAsync();
+            mPreviewing = true;
+            mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
+        }
+    }
+
+    // Prepares media recorder.
+    private void initializeRecorder() {
+        Log.v(TAG, "initializeRecorder");
+        // If the mCameraDevice is null, then this activity is going to finish
+        if (mActivity.mCameraDevice == null) return;
+
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Set the SurfaceView to visible so the surface gets created.
+            // surfaceCreated() is called immediately when the visibility is
+            // changed to visible. Thus, mSurfaceViewReady should become true
+            // right after calling setVisibility().
+            mUI.showSurfaceView();
+            if (!mUI.isSurfaceViewReady()) return;
+        }
+
+        Intent intent = mActivity.getIntent();
+        Bundle myExtras = intent.getExtras();
+
+        long requestedSizeLimit = 0;
+        closeVideoFileDescriptor();
+        if (mIsVideoCaptureIntent && myExtras != null) {
+            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            if (saveUri != null) {
+                try {
+                    mVideoFileDescriptor =
+                            mContentResolver.openFileDescriptor(saveUri, "rw");
+                    mCurrentVideoUri = saveUri;
+                } catch (java.io.FileNotFoundException ex) {
+                    // invalid uri
+                    Log.e(TAG, ex.toString());
+                }
+            }
+            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+        }
+        mMediaRecorder = new MediaRecorder();
+
+        setupMediaRecorderPreviewDisplay();
+        // Unlock the camera object before passing it to media recorder.
+        mActivity.mCameraDevice.unlock();
+        mActivity.mCameraDevice.waitDone();
+        mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera());
+        if (!mCaptureTimeLapse) {
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+        }
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setProfile(mProfile);
+        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
+        if (mCaptureTimeLapse) {
+            double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
+            setCaptureRate(mMediaRecorder, fps);
+        }
+
+        setRecordLocation();
+
+        // Set output file.
+        // Try Uri in the intent first. If it doesn't exist, use our own
+        // instead.
+        if (mVideoFileDescriptor != null) {
+            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+        } else {
+            generateVideoFilename(mProfile.fileFormat);
+            mMediaRecorder.setOutputFile(mVideoFilename);
+        }
+
+        // Set maximum file size.
+        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+            maxFileSize = requestedSizeLimit;
+        }
+
+        try {
+            mMediaRecorder.setMaxFileSize(maxFileSize);
+        } catch (RuntimeException exception) {
+            // We are going to ignore failure of setMaxFileSize here, as
+            // a) The composer selected may simply not support it, or
+            // b) The underlying media framework may not handle 64-bit range
+            // on the size restriction.
+        }
+
+        // See android.hardware.Camera.Parameters.setRotation for
+        // documentation.
+        // Note that mOrientation here is the device orientation, which is the opposite of
+        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
+        // 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];
+            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+                rotation = (info.orientation - mOrientation + 360) % 360;
+            } else {  // back-facing camera
+                rotation = (info.orientation + mOrientation) % 360;
+            }
+        }
+        mMediaRecorder.setOrientationHint(rotation);
+
+        try {
+            mMediaRecorder.prepare();
+        } catch (IOException e) {
+            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
+            releaseMediaRecorder();
+            throw new RuntimeException(e);
+        }
+
+        mMediaRecorder.setOnErrorListener(this);
+        mMediaRecorder.setOnInfoListener(this);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private static void setCaptureRate(MediaRecorder recorder, double fps) {
+        recorder.setCaptureRate(fps);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setRecordLocation() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            Location loc = mLocationManager.getCurrentLocation();
+            if (loc != null) {
+                mMediaRecorder.setLocation((float) loc.getLatitude(),
+                        (float) loc.getLongitude());
+            }
+        }
+    }
+
+    private void initializeEffectsPreview() {
+        Log.v(TAG, "initializeEffectsPreview");
+        // If the mCameraDevice is null, then this activity is going to finish
+        if (mActivity.mCameraDevice == null) return;
+
+        boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE);
+
+        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+
+        mEffectsDisplayResult = false;
+        mEffectsRecorder = new EffectsRecorder(mActivity);
+
+        // TODO: Confirm none of the following need to go to initializeEffectsRecording()
+        // and none of these change even when the preview is not refreshed.
+        mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
+        mEffectsRecorder.setCamera(mActivity.mCameraDevice);
+        mEffectsRecorder.setCameraFacing(info.facing);
+        mEffectsRecorder.setProfile(mProfile);
+        mEffectsRecorder.setEffectsListener(this);
+        mEffectsRecorder.setOnInfoListener(this);
+        mEffectsRecorder.setOnErrorListener(this);
+
+        // The input of effects recorder is affected by
+        // android.hardware.Camera.setDisplayOrientation. Its value only
+        // compensates the camera orientation (no Display.getRotation). So the
+        // orientation hint here should only consider sensor orientation.
+        int orientation = 0;
+        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
+            orientation = mOrientation;
+        }
+        mEffectsRecorder.setOrientationHint(orientation);
+
+        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+        mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(),
+                screenNail.getWidth(), screenNail.getHeight());
+
+        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+                ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+            mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
+        } else {
+            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+        }
+    }
+
+    private void initializeEffectsRecording() {
+        Log.v(TAG, "initializeEffectsRecording");
+
+        Intent intent = mActivity.getIntent();
+        Bundle myExtras = intent.getExtras();
+
+        long requestedSizeLimit = 0;
+        closeVideoFileDescriptor();
+        if (mIsVideoCaptureIntent && myExtras != null) {
+            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            if (saveUri != null) {
+                try {
+                    mVideoFileDescriptor =
+                            mContentResolver.openFileDescriptor(saveUri, "rw");
+                    mCurrentVideoUri = saveUri;
+                } catch (java.io.FileNotFoundException ex) {
+                    // invalid uri
+                    Log.e(TAG, ex.toString());
+                }
+            }
+            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+        }
+
+        mEffectsRecorder.setProfile(mProfile);
+        // important to set the capture rate to zero if not timelapsed, since the
+        // effectsrecorder object does not get created again for each recording
+        // session
+        if (mCaptureTimeLapse) {
+            mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
+        } else {
+            mEffectsRecorder.setCaptureRate(0);
+        }
+
+        // Set output file
+        if (mVideoFileDescriptor != null) {
+            mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+        } else {
+            generateVideoFilename(mProfile.fileFormat);
+            mEffectsRecorder.setOutputFile(mVideoFilename);
+        }
+
+        // Set maximum file size.
+        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+            maxFileSize = requestedSizeLimit;
+        }
+        mEffectsRecorder.setMaxFileSize(maxFileSize);
+        mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
+    }
+
+
+    private void releaseMediaRecorder() {
+        Log.v(TAG, "Releasing media recorder.");
+        if (mMediaRecorder != null) {
+            cleanupEmptyFile();
+            mMediaRecorder.reset();
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+        }
+        mVideoFilename = null;
+    }
+
+    private void releaseEffectsRecorder() {
+        Log.v(TAG, "Releasing effects recorder.");
+        if (mEffectsRecorder != null) {
+            cleanupEmptyFile();
+            mEffectsRecorder.release();
+            mEffectsRecorder = null;
+        }
+        mEffectType = EffectsRecorder.EFFECT_NONE;
+        mVideoFilename = null;
+    }
+
+    private void generateVideoFilename(int outputFileFormat) {
+        long dateTaken = System.currentTimeMillis();
+        String title = createName(dateTaken);
+        // Used when emailing.
+        String filename = title + convertOutputFormatToFileExt(outputFileFormat);
+        String mime = convertOutputFormatToMimeType(outputFileFormat);
+        String path = Storage.DIRECTORY + '/' + filename;
+        String tmpPath = path + ".tmp";
+        mCurrentVideoValues = new ContentValues(9);
+        mCurrentVideoValues.put(Video.Media.TITLE, title);
+        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
+        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
+        mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000);
+        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
+        mCurrentVideoValues.put(Video.Media.DATA, path);
+        mCurrentVideoValues.put(Video.Media.RESOLUTION,
+                Integer.toString(mProfile.videoFrameWidth) + "x" +
+                Integer.toString(mProfile.videoFrameHeight));
+        Location loc = mLocationManager.getCurrentLocation();
+        if (loc != null) {
+            mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
+            mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
+        }
+        mVideoFilename = tmpPath;
+        Log.v(TAG, "New video filename: " + mVideoFilename);
+    }
+
+    private void saveVideo() {
+        if (mVideoFileDescriptor == null) {
+            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
+            if (duration > 0) {
+                if (mCaptureTimeLapse) {
+                    duration = getTimeLapseVideoLength(duration);
+                }
+            } else {
+                Log.w(TAG, "Video duration <= 0 : " + duration);
+            }
+            mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename,
+                    duration, mCurrentVideoValues,
+                    mOnVideoSavedListener, mContentResolver);
+        }
+        mCurrentVideoValues = null;
+    }
+
+    private void deleteVideoFile(String fileName) {
+        Log.v(TAG, "Deleting video " + fileName);
+        File f = new File(fileName);
+        if (!f.delete()) {
+            Log.v(TAG, "Could not delete " + fileName);
+        }
+    }
+
+    private PreferenceGroup filterPreferenceScreenByIntent(
+            PreferenceGroup screen) {
+        Intent intent = mActivity.getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+            CameraSettings.removePreferenceFromScreen(screen,
+                    CameraSettings.KEY_VIDEO_QUALITY);
+        }
+
+        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+            CameraSettings.removePreferenceFromScreen(screen,
+                    CameraSettings.KEY_VIDEO_QUALITY);
+        }
+        return screen;
+    }
+
+    // from MediaRecorder.OnErrorListener
+    @Override
+    public void onError(MediaRecorder mr, int what, int extra) {
+        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+            // We may have run out of space on the sdcard.
+            stopVideoRecording();
+            mActivity.updateStorageSpaceAndHint();
+        }
+    }
+
+    // from MediaRecorder.OnInfoListener
+    @Override
+    public void onInfo(MediaRecorder mr, int what, int extra) {
+        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+            if (mMediaRecorderRecording) onStopVideoRecording();
+        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+            if (mMediaRecorderRecording) onStopVideoRecording();
+
+            // Show the toast.
+            Toast.makeText(mActivity, R.string.video_reach_size_limit,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    /*
+     * Make sure we're not recording music playing in the background, ask the
+     * MediaPlaybackService to pause playback.
+     */
+    private void pauseAudioPlayback() {
+        // Shamelessly copied from MediaPlaybackService.java, which
+        // should be public, but isn't.
+        Intent i = new Intent("com.android.music.musicservicecommand");
+        i.putExtra("command", "pause");
+
+        mActivity.sendBroadcast(i);
+    }
+
+    // For testing.
+    public boolean isRecording() {
+        return mMediaRecorderRecording;
+    }
+
+    private void startVideoRecording() {
+        Log.v(TAG, "startVideoRecording");
+        mUI.enablePreviewThumb(false);
+        mActivity.setSwipingEnabled(false);
+
+        mActivity.updateStorageSpaceAndHint();
+        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+            Log.v(TAG, "Storage issue, ignore the start request");
+            return;
+        }
+
+        if (!mActivity.mCameraDevice.waitDone()) return;
+        mCurrentVideoUri = null;
+        if (effectsActive()) {
+            initializeEffectsRecording();
+            if (mEffectsRecorder == null) {
+                Log.e(TAG, "Fail to initialize effect recorder");
+                return;
+            }
+        } else {
+            initializeRecorder();
+            if (mMediaRecorder == null) {
+                Log.e(TAG, "Fail to initialize media recorder");
+                return;
+            }
+        }
+
+        pauseAudioPlayback();
+
+        if (effectsActive()) {
+            try {
+                mEffectsRecorder.startRecording();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not start effects recorder. ", e);
+                releaseEffectsRecorder();
+                return;
+            }
+        } else {
+            try {
+                mMediaRecorder.start(); // Recording is now started
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not start media recorder. ", e);
+                releaseMediaRecorder();
+                // If start fails, frameworks will not lock the camera for us.
+                mActivity.mCameraDevice.lock();
+                return;
+            }
+        }
+
+        // Make sure the video recording has started before announcing
+        // this in accessibility.
+        AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(),
+                mActivity.getString(R.string.video_recording_started));
+
+        // The parameters might have been altered by MediaRecorder already.
+        // We need to force mCameraDevice to refresh before getting it.
+        mActivity.mCameraDevice.refreshParameters();
+        // The parameters may have been changed by MediaRecorder upon starting
+        // recording. We need to alter the parameters if we support camcorder
+        // zoom. To reduce latency when setting the parameters during zoom, we
+        // update mParameters here once.
+        if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
+            mParameters = mActivity.mCameraDevice.getParameters();
+        }
+
+        mUI.enableCameraControls(false);
+
+        mMediaRecorderRecording = true;
+        mActivity.getOrientationManager().lockOrientation();
+        mRecordingStartTime = SystemClock.uptimeMillis();
+        mUI.showRecordingUI(true, mParameters.isZoomSupported());
+
+        updateRecordingTime();
+        keepScreenOn();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_START, "Video");
+    }
+
+    private void showCaptureResult() {
+        mIsInReviewMode = true;
+        Bitmap bitmap = null;
+        if (mVideoFileDescriptor != null) {
+            bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
+                    mDesiredPreviewWidth);
+        } else if (mCurrentVideoFilename != null) {
+            bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
+                    mDesiredPreviewWidth);
+        }
+        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();
+            boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
+            bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
+            mUI.showReviewImage(bitmap);
+        }
+
+        mUI.showReviewControls();
+        mUI.enableCameraControls(false);
+        mUI.showTimeLapseUI(false);
+    }
+
+    private void hideAlert() {
+        mUI.enableCameraControls(true);
+        mUI.hideReviewUI();
+        if (mCaptureTimeLapse) {
+            mUI.showTimeLapseUI(true);
+        }
+    }
+
+    private boolean stopVideoRecording() {
+        Log.v(TAG, "stopVideoRecording");
+        mActivity.setSwipingEnabled(true);
+        mActivity.showSwitcher();
+
+        boolean fail = false;
+        if (mMediaRecorderRecording) {
+            boolean shouldAddToMediaStoreNow = false;
+
+            try {
+                if (effectsActive()) {
+                    // This is asynchronous, so we can't add to media store now because thumbnail
+                    // may not be ready. In such case saveVideo() is called later
+                    // through a callback from the MediaEncoderFilter to EffectsRecorder,
+                    // and then to the VideoModule.
+                    mEffectsRecorder.stopRecording();
+                } else {
+                    mMediaRecorder.setOnErrorListener(null);
+                    mMediaRecorder.setOnInfoListener(null);
+                    mMediaRecorder.stop();
+                    shouldAddToMediaStoreNow = true;
+                }
+                mCurrentVideoFilename = mVideoFilename;
+                Log.v(TAG, "stopVideoRecording: Setting current video filename: "
+                        + mCurrentVideoFilename);
+                AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(),
+                        mActivity.getString(R.string.video_recording_stopped));
+            } catch (RuntimeException e) {
+                Log.e(TAG, "stop fail",  e);
+                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
+                fail = true;
+            }
+            mMediaRecorderRecording = false;
+            mActivity.getOrientationManager().unlockOrientation();
+
+            // If the activity is paused, this means activity is interrupted
+            // during recording. Release the camera as soon as possible because
+            // face unlock or other applications may need to use the camera.
+            // However, if the effects are active, then we can only release the
+            // camera and cannot release the effects recorder since that will
+            // stop the graph. It is possible to separate out the Camera release
+            // part and the effects release part. However, the effects recorder
+            // does hold on to the camera, hence, it needs to be "disconnected"
+            // from the camera in the closeCamera call.
+            if (mPaused) {
+                // Closing only the camera part if effects active. Effects will
+                // be closed in the callback from effects.
+                boolean closeEffects = !effectsActive();
+                closeCamera(closeEffects);
+            }
+
+            mUI.showRecordingUI(false, mParameters.isZoomSupported());
+            if (!mIsVideoCaptureIntent) {
+                mUI.enableCameraControls(true);
+            }
+            // The orientation was fixed during video recording. Now make it
+            // reflect the device orientation as video recording is stopped.
+            mUI.setOrientationIndicator(0, true);
+            keepScreenOnAwhile();
+            if (shouldAddToMediaStoreNow) {
+                saveVideo();
+            }
+        }
+        // always release media recorder if no effects running
+        if (!effectsActive()) {
+            releaseMediaRecorder();
+            if (!mPaused) {
+                mActivity.mCameraDevice.lock();
+                mActivity.mCameraDevice.waitDone();
+                if (ApiHelper.HAS_SURFACE_TEXTURE &&
+                    !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+                    stopPreview();
+                    // Switch back to use SurfaceTexture for preview.
+                    ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener(
+                            mFrameDrawnListener);
+                    startPreview();
+                }
+            }
+        }
+        // Update the parameters here because the parameters might have been altered
+        // by MediaRecorder.
+        if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
+                    UsageStatistics.ACTION_CAPTURE_DONE, "Video",
+                    SystemClock.uptimeMillis() - mRecordingStartTime);
+        return fail;
+    }
+
+    private void resetScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    private void keepScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
+        long seconds = milliSeconds / 1000; // round down to compute seconds
+        long minutes = seconds / 60;
+        long hours = minutes / 60;
+        long remainderMinutes = minutes - (hours * 60);
+        long remainderSeconds = seconds - (minutes * 60);
+
+        StringBuilder timeStringBuilder = new StringBuilder();
+
+        // Hours
+        if (hours > 0) {
+            if (hours < 10) {
+                timeStringBuilder.append('0');
+            }
+            timeStringBuilder.append(hours);
+
+            timeStringBuilder.append(':');
+        }
+
+        // Minutes
+        if (remainderMinutes < 10) {
+            timeStringBuilder.append('0');
+        }
+        timeStringBuilder.append(remainderMinutes);
+        timeStringBuilder.append(':');
+
+        // Seconds
+        if (remainderSeconds < 10) {
+            timeStringBuilder.append('0');
+        }
+        timeStringBuilder.append(remainderSeconds);
+
+        // Centi seconds
+        if (displayCentiSeconds) {
+            timeStringBuilder.append('.');
+            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
+            if (remainderCentiSeconds < 10) {
+                timeStringBuilder.append('0');
+            }
+            timeStringBuilder.append(remainderCentiSeconds);
+        }
+
+        return timeStringBuilder.toString();
+    }
+
+    private long getTimeLapseVideoLength(long deltaMs) {
+        // For better approximation calculate fractional number of frames captured.
+        // This will update the video time at a higher resolution.
+        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
+        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
+    }
+
+    private void updateRecordingTime() {
+        if (!mMediaRecorderRecording) {
+            return;
+        }
+        long now = SystemClock.uptimeMillis();
+        long delta = now - mRecordingStartTime;
+
+        // Starting a minute before reaching the max duration
+        // limit, we'll countdown the remaining time instead.
+        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
+                && delta >= mMaxVideoDurationInMs - 60000);
+
+        long deltaAdjusted = delta;
+        if (countdownRemainingTime) {
+            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
+        }
+        String text;
+
+        long targetNextUpdateDelay;
+        if (!mCaptureTimeLapse) {
+            text = millisecondToTimeString(deltaAdjusted, false);
+            targetNextUpdateDelay = 1000;
+        } else {
+            // The length of time lapse video is different from the length
+            // of the actual wall clock time elapsed. Display the video length
+            // only in format hh:mm:ss.dd, where dd are the centi seconds.
+            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
+            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
+        }
+
+        mUI.setRecordingTime(text);
+
+        if (mRecordingTimeCountsDown != countdownRemainingTime) {
+            // Avoid setting the color on every update, do it only
+            // when it needs changing.
+            mRecordingTimeCountsDown = countdownRemainingTime;
+
+            int color = mActivity.getResources().getColor(countdownRemainingTime
+                    ? R.color.recording_time_remaining_text
+                    : R.color.recording_time_elapsed_text);
+
+            mUI.setRecordingTimeTextColor(color);
+        }
+
+        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
+        mHandler.sendEmptyMessageDelayed(
+                UPDATE_RECORD_TIME, actualNextUpdateDelay);
+    }
+
+    private static boolean isSupported(String value, List<String> supported) {
+        return supported == null ? false : supported.indexOf(value) >= 0;
+    }
+
+    @SuppressWarnings("deprecation")
+    private void setCameraParameters() {
+        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
+
+        // Set flash mode.
+        String flashMode;
+        if (mActivity.mShowCameraAppView) {
+            flashMode = mPreferences.getString(
+                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
+                    mActivity.getString(R.string.pref_camera_video_flashmode_default));
+        } else {
+            flashMode = Parameters.FLASH_MODE_OFF;
+        }
+        List<String> supportedFlash = mParameters.getSupportedFlashModes();
+        if (isSupported(flashMode, supportedFlash)) {
+            mParameters.setFlashMode(flashMode);
+        } else {
+            flashMode = mParameters.getFlashMode();
+            if (flashMode == null) {
+                flashMode = mActivity.getString(
+                        R.string.pref_camera_flashmode_no_flash);
+            }
+        }
+
+        // Set white balance parameter.
+        String whiteBalance = mPreferences.getString(
+                CameraSettings.KEY_WHITE_BALANCE,
+                mActivity.getString(R.string.pref_camera_whitebalance_default));
+        if (isSupported(whiteBalance,
+                mParameters.getSupportedWhiteBalance())) {
+            mParameters.setWhiteBalance(whiteBalance);
+        } else {
+            whiteBalance = mParameters.getWhiteBalance();
+            if (whiteBalance == null) {
+                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+            }
+        }
+
+        // Set zoom.
+        if (mParameters.isZoomSupported()) {
+            mParameters.setZoom(mZoomValue);
+        }
+
+        // Set continuous autofocus.
+        List<String> supportedFocus = mParameters.getSupportedFocusModes();
+        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
+            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+        }
+
+        mParameters.set(Util.RECORDING_HINT, Util.TRUE);
+
+        // Enable video stabilization. Convenience methods not available in API
+        // level <= 14
+        String vstabSupported = mParameters.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            mParameters.set("video-stabilization", "true");
+        }
+
+        // Set picture size.
+        // The logic here is different from the logic in still-mode camera.
+        // There we determine the preview size based on the picture size, but
+        // here we determine the picture size based on the preview size.
+        List<Size> supported = mParameters.getSupportedPictureSizes();
+        Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
+                (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
+        Size original = mParameters.getPictureSize();
+        if (!original.equals(optimalSize)) {
+            mParameters.setPictureSize(optimalSize.width, optimalSize.height);
+        }
+        Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
+                optimalSize.height);
+
+        // Set JPEG quality.
+        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+                CameraProfile.QUALITY_HIGH);
+        mParameters.setJpegQuality(jpegQuality);
+
+        mActivity.mCameraDevice.setParameters(mParameters);
+        // Keep preview size up to date.
+        mParameters = mActivity.mCameraDevice.getParameters();
+
+        updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+    }
+
+    private void updateCameraScreenNailSize(int width, int height) {
+        if (!ApiHelper.HAS_SURFACE_TEXTURE) return;
+
+        if (mCameraDisplayOrientation % 180 != 0) {
+            int tmp = width;
+            width = height;
+            height = tmp;
+        }
+
+        CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
+        int oldWidth = screenNail.getWidth();
+        int oldHeight = screenNail.getHeight();
+
+        if (oldWidth != width || oldHeight != height) {
+            screenNail.setSize(width, height);
+            screenNail.enableAspectRatioClamping();
+            mActivity.notifyScreenNailChanged();
+        }
+
+        if (screenNail.getSurfaceTexture() == null) {
+            screenNail.acquireSurfaceTexture();
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_EFFECT_BACKDROPPER:
+                if (resultCode == Activity.RESULT_OK) {
+                    // onActivityResult() runs before onResume(), so this parameter will be
+                    // seen by startPreview from onResume()
+                    mEffectUriFromGallery = data.getData().toString();
+                    Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
+                    mResetEffect = false;
+                } else {
+                    mEffectUriFromGallery = null;
+                    Log.w(TAG, "No URI from gallery");
+                    mResetEffect = true;
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onEffectsUpdate(int effectId, int effectMsg) {
+        Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
+        if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
+            // Effects have shut down. Hide learning message if any,
+            // and restart regular preview.
+            checkQualityAndStartPreview();
+        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
+            // This follows the codepath from onStopVideoRecording.
+            if (mEffectsDisplayResult) {
+                saveVideo();
+                if (mIsVideoCaptureIntent) {
+                    if (mQuickCapture) {
+                        doReturnToCaller(true);
+                    } else {
+                        showCaptureResult();
+                    }
+                }
+            }
+            mEffectsDisplayResult = false;
+            // In onPause, these were not called if the effects were active. We
+            // had to wait till the effects recording is complete to do this.
+            if (mPaused) {
+                closeVideoFileDescriptor();
+            }
+        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
+            // Enable the shutter button once the preview is complete.
+            mUI.enableShutter(true);
+        }
+        // In onPause, this was not called if the effects were active. We had to
+        // wait till the effects completed to do this.
+        if (mPaused) {
+            Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
+            closeEffects();
+        }
+    }
+
+    public void onCancelBgTraining(View v) {
+        // Write default effect out to shared prefs
+        writeDefaultEffectToPrefs();
+        // Tell VideoCamer to re-init based on new shared pref values.
+        onSharedPreferenceChanged();
+    }
+
+    @Override
+    public synchronized void onEffectsError(Exception exception, String fileName) {
+        // TODO: Eventually we may want to show the user an error dialog, and then restart the
+        // camera and encoder gracefully. For now, we just delete the file and bail out.
+        if (fileName != null && new File(fileName).exists()) {
+            deleteVideoFile(fileName);
+        }
+        try {
+            if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
+                    .isInstance(exception)) {
+                Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
+                return;
+            }
+        } catch (ClassNotFoundException ex) {
+            Log.w(TAG, ex);
+        }
+        throw new RuntimeException("Error during recording!", exception);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.v(TAG, "onConfigurationChanged");
+        setDisplayOrientation();
+    }
+
+    @Override
+    public void onOverriddenPreferencesClicked() {
+    }
+
+    @Override
+    // TODO: Delete this after old camera code is removed
+    public void onRestorePreferencesClicked() {
+    }
+
+    private boolean effectsActive() {
+        return (mEffectType != EffectsRecorder.EFFECT_NONE);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged() {
+        // ignore the events after "onPause()" or preview has not started yet
+        if (mPaused) return;
+        synchronized (mPreferences) {
+            // If mCameraDevice is not ready then we can set the parameter in
+            // startPreview().
+            if (mActivity.mCameraDevice == null) return;
+
+            boolean recordLocation = RecordLocationPreference.get(
+                    mPreferences, mContentResolver);
+            mLocationManager.recordLocation(recordLocation);
+
+            // Check if the current effects selection has changed
+            if (updateEffectSelection()) return;
+
+            readVideoPreferences();
+            mUI.showTimeLapseUI(mCaptureTimeLapse);
+            // We need to restart the preview if preview size is changed.
+            Size size = mParameters.getPreviewSize();
+            if (size.width != mDesiredPreviewWidth
+                    || size.height != mDesiredPreviewHeight) {
+                if (!effectsActive()) {
+                    stopPreview();
+                } else {
+                    mEffectsRecorder.release();
+                    mEffectsRecorder = null;
+                }
+                resizeForPreviewAspectRatio();
+                startPreview(); // Parameters will be set in startPreview().
+            } else {
+                setCameraParameters();
+            }
+            mUI.updateOnScreenIndicators(mParameters, mPreferences);
+        }
+    }
+
+    protected void setCameraId(int cameraId) {
+        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+        pref.setValue("" + cameraId);
+    }
+
+    private void switchCamera() {
+        if (mPaused) return;
+
+        Log.d(TAG, "Start to switch camera.");
+        mCameraId = mPendingSwitchCameraId;
+        mPendingSwitchCameraId = -1;
+        setCameraId(mCameraId);
+
+        closeCamera();
+        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
+        mUI.initializeZoom(mParameters);
+        mUI.setOrientationIndicator(0, false);
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Start switch camera animation. Post a message because
+            // onFrameAvailable from the old camera may already exist.
+            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+        }
+        mUI.updateOnScreenIndicators(mParameters, mPreferences);
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mHandler.sendEmptyMessage(SWITCH_CAMERA);
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    private boolean updateEffectSelection() {
+        int previousEffectType = mEffectType;
+        Object previousEffectParameter = mEffectParameter;
+        mEffectType = CameraSettings.readEffectType(mPreferences);
+        mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+
+        if (mEffectType == previousEffectType) {
+            if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
+            if (mEffectParameter.equals(previousEffectParameter)) return false;
+        }
+        Log.v(TAG, "New effect selection: " + mPreferences.getString(
+                CameraSettings.KEY_VIDEO_EFFECT, "none"));
+
+        if (mEffectType == EffectsRecorder.EFFECT_NONE) {
+            // Stop effects and return to normal preview
+            mEffectsRecorder.stopPreview();
+            mPreviewing = false;
+            return true;
+        }
+        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+            ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+            // Request video from gallery to use for background
+            Intent i = new Intent(Intent.ACTION_PICK);
+            i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
+                             "video/*");
+            i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+            mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
+            return true;
+        }
+        if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
+            // Stop regular preview and start effects.
+            stopPreview();
+            checkQualityAndStartPreview();
+        } else {
+            // Switch currently running effect
+            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+        }
+        return true;
+    }
+
+    // Verifies that the current preview view size is correct before starting
+    // preview. If not, resets the surface texture and resizes the view.
+    private void checkQualityAndStartPreview() {
+        readVideoPreferences();
+        mUI.showTimeLapseUI(mCaptureTimeLapse);
+        Size size = mParameters.getPreviewSize();
+        if (size.width != mDesiredPreviewWidth
+                || size.height != mDesiredPreviewHeight) {
+            resizeForPreviewAspectRatio();
+        }
+        // Start up preview again
+        startPreview();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mSwitchingCamera) return true;
+        return mUI.dispatchTouchEvent(m);
+    }
+
+    private void initializeVideoSnapshot() {
+        if (mParameters == null) return;
+        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+            mActivity.setSingleTapUpListener(mUI.getPreview());
+            // Show the tap to focus toast if this is the first start.
+            if (mPreferences.getBoolean(
+                        CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
+                // Delay the toast for one second to wait for orientation.
+                mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
+            }
+        } else {
+            mActivity.setSingleTapUpListener(null);
+        }
+    }
+
+    void showVideoSnapshotUI(boolean enabled) {
+        if (mParameters == null) return;
+        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+            if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) {
+                ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+            } else {
+                mUI.showPreviewBorder(enabled);
+            }
+            mUI.enableShutter(!enabled);
+        }
+    }
+
+    @Override
+    public void updateCameraAppView() {
+        if (!mPreviewing || mParameters.getFlashMode() == null) return;
+
+        // When going to and back from gallery, we need to turn off/on the flash.
+        if (!mActivity.mShowCameraAppView) {
+            if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
+                mRestoreFlash = false;
+                return;
+            }
+            mRestoreFlash = true;
+            setCameraParameters();
+        } else if (mRestoreFlash) {
+            mRestoreFlash = false;
+            setCameraParameters();
+        }
+    }
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+        mUI.onFullScreenChanged(full);
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            if (mActivity.mCameraScreenNail != null) {
+                ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
+            }
+            return;
+        }
+    }
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        @Override
+        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
+            Log.v(TAG, "onPictureTaken");
+            mSnapshotInProgress = false;
+            showVideoSnapshotUI(false);
+            storeImage(jpegData, mLocation);
+        }
+    }
+
+    private void storeImage(final byte[] data, Location loc) {
+        long dateTaken = System.currentTimeMillis();
+        String title = Util.createJpegName(dateTaken);
+        ExifInterface exif = Exif.getExif(data);
+        int orientation = Exif.getOrientation(exif);
+        Size s = mParameters.getPictureSize();
+        mActivity.getMediaSaveService().addImage(
+                data, title, dateTaken, loc, s.width, s.height, orientation,
+                exif, mOnPhotoSavedListener, mContentResolver);
+    }
+
+    private boolean resetEffect() {
+        if (mResetEffect) {
+            String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
+                    mPrefVideoEffectDefault);
+            if (!mPrefVideoEffectDefault.equals(value)) {
+                writeDefaultEffectToPrefs();
+                return true;
+            }
+        }
+        mResetEffect = true;
+        return false;
+    }
+
+    private String convertOutputFormatToMimeType(int outputFileFormat) {
+        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+            return "video/mp4";
+        }
+        return "video/3gpp";
+    }
+
+    private String convertOutputFormatToFileExt(int outputFileFormat) {
+        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+            return ".mp4";
+        }
+        return ".3gp";
+    }
+
+    private void closeVideoFileDescriptor() {
+        if (mVideoFileDescriptor != null) {
+            try {
+                mVideoFileDescriptor.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close fd", e);
+            }
+            mVideoFileDescriptor = null;
+        }
+    }
+
+    private void showTapToSnapshotToast() {
+        new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
+                .show();
+        // Clear the preference.
+        Editor editor = mPreferences.edit();
+        editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
+        editor.apply();
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return true;
+    }
+
+    // required by OnPreferenceChangedListener
+    @Override
+    public void onCameraPickerClicked(int cameraId) {
+        if (mPaused || mPendingSwitchCameraId != -1) return;
+
+        mPendingSwitchCameraId = cameraId;
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            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.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+            // Disable all camera controls.
+            mSwitchingCamera = true;
+        } else {
+            switchCamera();
+        }
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return !mIsVideoCaptureIntent;
+    }
+
+    @Override
+    public boolean needsPieMenu() {
+        return true;
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+        mUI.onShowSwitcherPopup();
+    }
+
+    @Override
+    public void onMediaSaveServiceConnected(MediaSaveService s) {
+        // do nothing.
+    }
+}
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
new file mode 100644
index 0000000..9fc0576
--- /dev/null
+++ b/src/com/android/camera/VideoUI.java
@@ -0,0 +1,545 @@
+/*
+ * 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.graphics.Bitmap;
+import android.hardware.Camera.Parameters;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.PreviewSurfaceView;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.RotateLayout;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.List;
+
+public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener,
+        PreviewGestures.SingleTapListener,
+        PreviewGestures.SwipeListener {
+    private final static String TAG = "CAM_VideoUI";
+    // module fields
+    private CameraActivity mActivity;
+    private View mRootView;
+    private PreviewFrameLayout mPreviewFrameLayout;
+    private boolean mSurfaceViewReady;
+    private PreviewSurfaceView mPreviewSurfaceView;
+    // An review image having same size as preview. It is displayed when
+    // recording is stopped in capture intent.
+    private ImageView mReviewImage;
+    private View mReviewCancelButton;
+    private View mReviewDoneButton;
+    private View mReviewPlayButton;
+    private ShutterButton mShutterButton;
+    private TextView mRecordingTimeView;
+    private LinearLayout mLabelsLinearLayout;
+    private View mTimeLapseLabel;
+    private RenderOverlay mRenderOverlay;
+    private PieRenderer mPieRenderer;
+    private VideoMenu mVideoMenu;
+    private AbstractSettingPopup mPopup;
+    private ZoomRenderer mZoomRenderer;
+    private PreviewGestures mGestures;
+    private View mMenu;
+    private View mBlocker;
+    private OnScreenIndicators mOnScreenIndicators;
+    private RotateLayout mRecordingTimeRect;
+    private VideoController mController;
+    private int mZoomMax;
+    private List<Integer> mZoomRatios;
+    private View mPreviewThumb;
+
+    public VideoUI(CameraActivity activity, VideoController controller, View parent) {
+        mActivity = activity;
+        mController = controller;
+        mRootView = parent;
+        mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true);
+        mPreviewSurfaceView = (PreviewSurfaceView) mRootView
+                .findViewById(R.id.preview_surface_view);
+        initializeMiscControls();
+        initializeControlByIntent();
+        initializeOverlay();
+    }
+
+    private void initializeControlByIntent() {
+        mBlocker = mActivity.findViewById(R.id.blocker);
+        mMenu = mActivity.findViewById(R.id.menu);
+        mMenu.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mPieRenderer != null) {
+                    mPieRenderer.showInCenter();
+                }
+            }
+        });
+        mOnScreenIndicators = new OnScreenIndicators(mActivity,
+                mActivity.findViewById(R.id.on_screen_indicators));
+        mOnScreenIndicators.resetToDefault();
+        if (mController.isVideoCaptureIntent()) {
+            mActivity.hideSwitcher();
+            ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
+            mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
+            // Cannot use RotateImageView for "done" and "cancel" button because
+            // the tablet layout uses RotateLayout, which cannot be cast to
+            // RotateImageView.
+            mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+            mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+            mReviewPlayButton = mActivity.findViewById(R.id.btn_play);
+            mReviewCancelButton.setVisibility(View.VISIBLE);
+            mReviewDoneButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewDoneClicked(v);
+                }
+            });
+            mReviewCancelButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewCancelClicked(v);
+                }
+            });
+            mReviewPlayButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewPlayClicked(v);
+                }
+            });
+        }
+    }
+
+    public boolean collapseCameraControls() {
+        boolean ret = false;
+        if (mPopup != null) {
+            dismissPopup(false);
+            ret = true;
+        }
+        return ret;
+    }
+
+    public boolean removeTopLevelPopup() {
+        if (mPopup != null) {
+            dismissPopup(true);
+            return true;
+        }
+        return false;
+    }
+
+    public void enableCameraControls(boolean enable) {
+        if (mGestures != null) {
+            mGestures.setZoomOnly(!enable);
+        }
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+        }
+    }
+
+    public void overrideSettings(final String... keyvalues) {
+        mVideoMenu.overrideSettings(keyvalues);
+    }
+
+    public View getPreview() {
+        return mPreviewFrameLayout;
+    }
+
+    public void setOrientationIndicator(int orientation, boolean animation) {
+        if (mGestures != null) {
+            mGestures.setOrientation(orientation);
+        }
+        // We change the orientation of the linearlayout only for phone UI
+        // because when in portrait the width is not enough.
+        if (mLabelsLinearLayout != null) {
+            if (((orientation / 90) & 1) == 0) {
+                mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
+            } else {
+                mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            }
+        }
+        mRecordingTimeRect.setOrientation(0, animation);
+    }
+
+    public SurfaceHolder getSurfaceHolder() {
+        return mPreviewSurfaceView.getHolder();
+    }
+
+    public void hideSurfaceView() {
+        mPreviewSurfaceView.setVisibility(View.GONE);
+    }
+
+    public void showSurfaceView() {
+        mPreviewSurfaceView.setVisibility(View.VISIBLE);
+    }
+
+    private void initializeOverlay() {
+        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+        if (mPieRenderer == null) {
+            mPieRenderer = new PieRenderer(mActivity);
+            mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer);
+            mPieRenderer.setPieListener(this);
+        }
+        mRenderOverlay.addRenderer(mPieRenderer);
+        if (mZoomRenderer == null) {
+            mZoomRenderer = new ZoomRenderer(mActivity);
+        }
+        mRenderOverlay.addRenderer(mZoomRenderer);
+        if (mGestures == null) {
+            mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
+        }
+        mGestures.setRenderOverlay(mRenderOverlay);
+        mGestures.reset();
+        mGestures.addTouchReceiver(mMenu);
+        mGestures.addUnclickableArea(mBlocker);
+        if (mController.isVideoCaptureIntent()) {
+            if (mReviewCancelButton != null) {
+                mGestures.addTouchReceiver(mReviewCancelButton);
+            }
+            if (mReviewDoneButton != null) {
+                mGestures.addTouchReceiver(mReviewDoneButton);
+            }
+            if (mReviewPlayButton != null) {
+                mGestures.addTouchReceiver(mReviewPlayButton);
+            }
+        }
+
+        mPreviewThumb = mActivity.findViewById(R.id.preview_thumb);
+        mPreviewThumb.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mActivity.gotoGallery();
+            }
+        });
+    }
+
+    public void setPrefChangedListener(OnPreferenceChangedListener listener) {
+        mVideoMenu.setListener(listener);
+    }
+
+    private void initializeMiscControls() {
+        mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame);
+        mPreviewFrameLayout.setOnLayoutChangeListener(mActivity);
+        mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
+        mShutterButton = mActivity.getShutterButton();
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
+        mShutterButton.setOnShutterButtonListener(mController);
+        mShutterButton.setVisibility(View.VISIBLE);
+        mShutterButton.requestFocus();
+        mShutterButton.enableTouch(true);
+        mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
+        mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
+        mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
+        // The R.id.labels can only be found in phone layout.
+        // That is, mLabelsLinearLayout should be null in tablet layout.
+        mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
+    }
+
+    public void updateOnScreenIndicators(Parameters param, ComboPreferences prefs) {
+      mOnScreenIndicators.updateFlashOnScreenIndicator(param.getFlashMode());
+      boolean location = RecordLocationPreference.get(
+              prefs, mActivity.getContentResolver());
+      mOnScreenIndicators.updateLocationIndicator(location);
+
+    }
+
+    public void setAspectRatio(double ratio) {
+        mPreviewFrameLayout.setAspectRatio(ratio);
+    }
+
+    public void showTimeLapseUI(boolean enable) {
+        if (mTimeLapseLabel != null) {
+            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private void openMenu() {
+        if (mPieRenderer != null) {
+            mPieRenderer.showInCenter();
+        }
+    }
+
+    public void showPopup(AbstractSettingPopup popup) {
+        mActivity.hideUI();
+        mBlocker.setVisibility(View.INVISIBLE);
+        setShowMenu(false);
+        mPopup = popup;
+        mPopup.setVisibility(View.VISIBLE);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.CENTER;
+        ((FrameLayout) mRootView).addView(mPopup, lp);
+        mGestures.addTouchReceiver(mPopup);
+    }
+
+    public void dismissPopup(boolean topLevelOnly) {
+        dismissPopup(topLevelOnly, true);
+    }
+
+    public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) {
+        // In review mode, we do not want to bring up the camera UI
+        if (mController.isInReviewMode()) return;
+
+        if (fullScreen) {
+            mActivity.showUI();
+            mBlocker.setVisibility(View.VISIBLE);
+        }
+        setShowMenu(fullScreen);
+        if (mPopup != null) {
+            mGestures.removeTouchReceiver(mPopup);
+            ((FrameLayout) mRootView).removeView(mPopup);
+            mPopup = null;
+        }
+        mVideoMenu.popupDismissed(topLevelPopupOnly);
+    }
+
+    public void onShowSwitcherPopup() {
+        hidePieRenderer();
+    }
+
+    public boolean hidePieRenderer() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+            return true;
+        }
+        return false;
+    }
+
+    // disable preview gestures after shutter is pressed
+    public void setShutterPressed(boolean pressed) {
+        if (mGestures == null) return;
+        mGestures.setEnabled(!pressed);
+    }
+
+    public void enableShutter(boolean enable) {
+        if (mShutterButton != null) {
+            mShutterButton.setEnabled(enable);
+        }
+    }
+
+    // PieListener
+    @Override
+    public void onPieOpened(int centerX, int centerY) {
+        dismissPopup(false, true);
+        mActivity.cancelActivityTouchHandling();
+        mActivity.setSwipingEnabled(false);
+    }
+
+    @Override
+    public void onPieClosed() {
+        mActivity.setSwipingEnabled(true);
+    }
+
+    public void showPreviewBorder(boolean enable) {
+        mPreviewFrameLayout.showBorder(enable);
+    }
+
+    // SingleTapListener
+    // Preview area is touched. Take a picture.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        mController.onSingleTapUp(view, x, y);
+    }
+
+    // SurfaceView callback
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.v(TAG, "Surface created");
+        mSurfaceViewReady = true;
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.v(TAG, "Surface destroyed");
+        mSurfaceViewReady = false;
+        mController.stopPreview();
+    }
+
+    public boolean isSurfaceViewReady() {
+        return mSurfaceViewReady;
+    }
+
+    public void showRecordingUI(boolean recording, boolean zoomSupported) {
+        mMenu.setVisibility(recording ? View.GONE : View.VISIBLE);
+        mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
+        if (recording) {
+            mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
+            mActivity.hideSwitcher();
+            mRecordingTimeView.setText("");
+            mRecordingTimeView.setVisibility(View.VISIBLE);
+            // The camera is not allowed to be accessed in older api levels during
+            // recording. It is therefore necessary to hide the zoom UI on older
+            // platforms.
+            // See the documentation of android.media.MediaRecorder.start() for
+            // further explanation.
+            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
+                // TODO: disable zoom UI here.
+            }
+        } else {
+            mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
+            mActivity.showSwitcher();
+            mRecordingTimeView.setVisibility(View.GONE);
+            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
+                // TODO: enable zoom UI here.
+            }
+        }
+    }
+
+    public void showReviewImage(Bitmap bitmap) {
+        mReviewImage.setImageBitmap(bitmap);
+        mReviewImage.setVisibility(View.VISIBLE);
+    }
+
+    public void showReviewControls() {
+        Util.fadeOut(mShutterButton);
+        Util.fadeIn(mReviewDoneButton);
+        Util.fadeIn(mReviewPlayButton);
+        mReviewImage.setVisibility(View.VISIBLE);
+        mMenu.setVisibility(View.GONE);
+        mOnScreenIndicators.setVisibility(View.GONE);
+    }
+
+    public void hideReviewUI() {
+        mReviewImage.setVisibility(View.GONE);
+        mShutterButton.setEnabled(true);
+        mMenu.setVisibility(View.VISIBLE);
+        mOnScreenIndicators.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewDoneButton);
+        Util.fadeOut(mReviewPlayButton);
+        Util.fadeIn(mShutterButton);
+    }
+
+    private void setShowMenu(boolean show) {
+        if (mOnScreenIndicators != null) {
+            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+        if (mMenu != null) {
+            mMenu.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public void onFullScreenChanged(boolean full) {
+        if (mGestures != null) {
+            mGestures.setEnabled(full);
+        }
+        if (mPopup != null) {
+            dismissPopup(false, full);
+        }
+        if (mRenderOverlay != null) {
+            // this can not happen in capture mode
+            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        setShowMenu(full);
+        if (mBlocker != null) {
+            // this can not happen in capture mode
+            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public void initializePopup(PreferenceGroup pref) {
+        mVideoMenu.initialize(pref);
+    }
+
+    public void initializeZoom(Parameters param) {
+        if (param == null || !param.isZoomSupported()) return;
+        mZoomMax = param.getMaxZoom();
+        mZoomRatios = param.getZoomRatios();
+        // Currently we use immediate zoom for fast zooming to get better UX and
+        // there is no plan to take advantage of the smooth zoom.
+        mZoomRenderer.setZoomMax(mZoomMax);
+        mZoomRenderer.setZoom(param.getZoom());
+        mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom()));
+        mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+    }
+
+    public void clickShutter() {
+        mShutterButton.performClick();
+    }
+
+    public void pressShutter(boolean pressed) {
+        mShutterButton.setPressed(pressed);
+    }
+
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mGestures != null && mRenderOverlay != null) {
+            return mGestures.dispatchTouch(m);
+        }
+        return false;
+    }
+
+    public void setRecordingTime(String text) {
+        mRecordingTimeView.setText(text);
+    }
+
+    public void setRecordingTimeTextColor(int color) {
+        mRecordingTimeView.setTextColor(color);
+    }
+
+    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
+        @Override
+        public void onZoomValueChanged(int index) {
+            int newZoom = mController.onZoomChanged(index);
+            if (mZoomRenderer != null) {
+                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
+            }
+        }
+
+        @Override
+        public void onZoomStart() {
+        }
+
+        @Override
+        public void onZoomEnd() {
+        }
+    }
+
+    @Override
+    public void onSwipe(int direction) {
+        if (direction == PreviewGestures.DIR_UP) {
+            openMenu();
+        }
+    }
+
+    /**
+     * Enable or disable the preview thumbnail for click events.
+     */
+    public void enablePreviewThumb(boolean enabled) {
+        if (enabled) {
+            mGestures.addTouchReceiver(mPreviewThumb);
+            mPreviewThumb.setVisibility(View.VISIBLE);
+        } else {
+            mGestures.removeTouchReceiver(mPreviewThumb);
+            mPreviewThumb.setVisibility(View.GONE);
+        }
+    }
+}
diff --git a/src/com/android/camera/drawable/TextDrawable.java b/src/com/android/camera/drawable/TextDrawable.java
new file mode 100644
index 0000000..60d8719
--- /dev/null
+++ b/src/com/android/camera/drawable/TextDrawable.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 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.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+
+
+public class TextDrawable extends Drawable {
+
+    private static final int DEFAULT_COLOR = Color.WHITE;
+    private static final int DEFAULT_TEXTSIZE = 15;
+
+    private Paint mPaint;
+    private CharSequence mText;
+    private int mIntrinsicWidth;
+    private int mIntrinsicHeight;
+    private boolean mUseDropShadow;
+
+    public TextDrawable(Resources res) {
+        this(res, "");
+    }
+
+    public TextDrawable(Resources res, CharSequence text) {
+        mText = text;
+        updatePaint();
+        float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
+                DEFAULT_TEXTSIZE, res.getDisplayMetrics());
+        mPaint.setTextSize(textSize);
+        mIntrinsicWidth = (int) (mPaint.measureText(mText, 0, mText.length()) + .5);
+        mIntrinsicHeight = mPaint.getFontMetricsInt(null);
+    }
+
+    private void updatePaint() {
+        if (mPaint == null) {
+            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        }
+        mPaint.setColor(DEFAULT_COLOR);
+        mPaint.setTextAlign(Align.CENTER);
+        if (mUseDropShadow) {
+            mPaint.setTypeface(Typeface.DEFAULT_BOLD);
+            mPaint.setShadowLayer(10, 0, 0, 0xff000000);
+        } else {
+            mPaint.setTypeface(Typeface.DEFAULT);
+            mPaint.setShadowLayer(0, 0, 0, 0);
+        }
+    }
+
+    public void setText(CharSequence txt) {
+        mText = txt;
+        if (txt == null) {
+            mIntrinsicWidth = 0;
+            mIntrinsicHeight = 0;
+        } else {
+            mIntrinsicWidth = (int) (mPaint.measureText(mText, 0, mText.length()) + .5);
+            mIntrinsicHeight = mPaint.getFontMetricsInt(null);
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mText != null) {
+            Rect bounds = getBounds();
+            canvas.drawText(mText, 0, mText.length(),
+                    bounds.centerX(), bounds.centerY(), mPaint);
+        }
+    }
+
+    public void setDropShadow(boolean shadow) {
+        mUseDropShadow = shadow;
+        updatePaint();
+    }
+
+    @Override
+    public int getOpacity() {
+        return mPaint.getAlpha();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mIntrinsicWidth;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mIntrinsicHeight;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter filter) {
+        mPaint.setColorFilter(filter);
+    }
+
+}
diff --git a/src/com/android/camera/ui/AbstractSettingPopup.java b/src/com/android/camera/ui/AbstractSettingPopup.java
new file mode 100644
index 0000000..783b6c7
--- /dev/null
+++ b/src/com/android/camera/ui/AbstractSettingPopup.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+
+// A popup window that shows one or more camera settings.
+abstract public class AbstractSettingPopup extends RotateLayout {
+    protected ViewGroup mSettingList;
+    protected TextView mTitle;
+
+    public AbstractSettingPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mTitle = (TextView) findViewById(R.id.title);
+        mSettingList = (ViewGroup) findViewById(R.id.settingList);
+    }
+
+    abstract public void reloadPreference();
+}
diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java
new file mode 100644
index 0000000..7940ae0
--- /dev/null
+++ b/src/com/android/camera/ui/CameraControls.java
@@ -0,0 +1,347 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+public class CameraControls extends RotatableLayout {
+
+    private static final String TAG = "CAM_Controls";
+
+    private View mBackgroundView;
+    private View mShutter;
+    private View mSwitcher;
+    private View mMenu;
+    private View mIndicators;
+    private View mPreview;
+    private Object mDisplayListener = null;
+    private int mLastRotation = 0;
+
+    public CameraControls(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initDisplayListener();
+    }
+
+    public CameraControls(Context context) {
+        super(context);
+        initDisplayListener();
+    }
+
+    public void initDisplayListener() {
+        if (ApiHelper.HAS_DISPLAY_LISTENER) {
+            mDisplayListener = new DisplayListener() {
+
+                @Override
+                public void onDisplayAdded(int arg0) {}
+
+                @Override
+                public void onDisplayChanged(int arg0) {
+                    checkLayoutFlip();
+                }
+
+                @Override
+                public void onDisplayRemoved(int arg0) {}
+            };
+        }
+    }
+
+    private void checkLayoutFlip() {
+        int currentRotation = Util.getDisplayRotation((Activity) getContext());
+        if ((currentRotation - mLastRotation + 360) % 360 == 180) {
+            mLastRotation = currentRotation;
+            flipChildren();
+            getParent().requestLayout();
+        }
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mBackgroundView = findViewById(R.id.blocker);
+        mSwitcher = findViewById(R.id.camera_switcher);
+        mShutter = findViewById(R.id.shutter_button);
+        mMenu = findViewById(R.id.menu);
+        mIndicators = findViewById(R.id.on_screen_indicators);
+        mPreview = findViewById(R.id.preview_thumb);
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        adjustControlsToRightPosition();
+        mLastRotation = Util.getDisplayRotation((Activity) getContext());
+        if (ApiHelper.HAS_DISPLAY_LISTENER) {
+            ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
+            .registerDisplayListener((DisplayListener) mDisplayListener, null);
+        }
+    }
+
+    @Override
+    public void onWindowVisibilityChanged(int visibility) {
+        if (visibility == View.VISIBLE) {
+            // Make sure when coming back from onPause, the layout is rotated correctly
+            checkLayoutFlip();
+        }
+    }
+
+    @Override
+    public void onDetachedFromWindow () {
+        super.onDetachedFromWindow();
+        if (ApiHelper.HAS_DISPLAY_LISTENER) {
+            ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE))
+            .unregisterDisplayListener((DisplayListener) mDisplayListener);
+        }
+    }
+
+    @Override
+    public void onLayout(boolean changed, int l, int t, int r, int b) {
+        mLastRotation = Util.getDisplayRotation((Activity) getContext());
+        int orientation = getResources().getConfiguration().orientation;
+        int size = getResources().getDimensionPixelSize(R.dimen.camera_controls_size);
+        int rotation = getUnifiedRotation();
+        adjustBackground();
+        // As l,t,r,b are positions relative to parents, we need to convert them
+        // to child's coordinates
+        r = r - l;
+        b = b - t;
+        l = 0;
+        t = 0;
+        for (int i = 0; i < getChildCount(); i++) {
+            View v = getChildAt(i);
+            v.layout(l, t, r, b);
+        }
+        Rect shutter = new Rect();
+        topRight(mPreview, l, t, r, b);
+        if (size > 0) {
+            // restrict controls to size
+            switch (rotation) {
+            case 0:
+            case 180:
+                l = (l + r - size) / 2;
+                r = l + size;
+                break;
+            case 90:
+            case 270:
+                t = (t + b - size) / 2;
+                b = t + size;
+                break;
+            }
+        }
+        center(mShutter, l, t, r, b, orientation, rotation, shutter);
+        center(mBackgroundView, l, t, r, b, orientation, rotation, new Rect());
+        toLeft(mSwitcher, shutter, rotation);
+        toRight(mMenu, shutter, rotation);
+        toRight(mIndicators, shutter, rotation);
+        View retake = findViewById(R.id.btn_retake);
+        if (retake != null) {
+            center(retake, shutter, rotation);
+            View cancel = findViewById(R.id.btn_cancel);
+            toLeft(cancel, shutter, rotation);
+            View done = findViewById(R.id.btn_done);
+            toRight(done, shutter, rotation);
+        }
+    }
+
+    private int getUnifiedRotation() {
+        // all the layout code assumes camera device orientation to be portrait
+        // adjust rotation for landscape
+        int orientation = getResources().getConfiguration().orientation;
+        int rotation = Util.getDisplayRotation((Activity) getContext());
+        int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT
+                : Configuration.ORIENTATION_LANDSCAPE;
+        if (camOrientation != orientation) {
+            return (rotation + 90) % 360;
+        }
+        return rotation;
+    }
+
+    private void center(View v, int l, int t, int r, int b, int orientation, int rotation, Rect result) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
+        switch (rotation) {
+        case 0:
+            // phone portrait; controls bottom
+            result.left = (r + l) / 2 - tw / 2 + lp.leftMargin;
+            result.right = (r + l) / 2 + tw / 2 - lp.rightMargin;
+            result.bottom = b - lp.bottomMargin;
+            result.top = b - th + lp.topMargin;
+            break;
+        case 90:
+            // phone landscape: controls right
+            result.right = r - lp.rightMargin;
+            result.left = r - tw + lp.leftMargin;
+            result.top = (b + t) / 2 - th / 2 + lp.topMargin;
+            result.bottom = (b + t) / 2 + th / 2 - lp.bottomMargin;
+            break;
+        case 180:
+            // phone upside down: controls top
+            result.left = (r + l) / 2 - tw / 2 + lp.leftMargin;
+            result.right = (r + l) / 2 + tw / 2 - lp.rightMargin;
+            result.top = t + lp.topMargin;
+            result.bottom = t + th - lp.bottomMargin;
+            break;
+        case 270:
+            // reverse landscape: controls left
+            result.left = l + lp.leftMargin;
+            result.right = l + tw - lp.rightMargin;
+            result.top = (b + t) / 2 - th / 2 + lp.topMargin;
+            result.bottom = (b + t) / 2 + th / 2 - lp.bottomMargin;
+            break;
+        }
+        v.layout(result.left, result.top, result.right, result.bottom);
+    }
+
+    private void center(View v, Rect other, int rotation) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
+        int cx = (other.left + other.right) / 2;
+        int cy = (other.top + other.bottom) / 2;
+        v.layout(cx - tw / 2 + lp.leftMargin,
+                cy - th / 2 + lp.topMargin,
+                cx + tw / 2 - lp.rightMargin,
+                cy + th / 2 - lp.bottomMargin);
+    }
+
+    private void toLeft(View v, Rect other, int rotation) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
+        int cx = (other.left + other.right) / 2;
+        int cy = (other.top + other.bottom) / 2;
+        int l = 0, r = 0, t = 0, b = 0;
+        switch (rotation) {
+        case 0:
+            // portrait, to left of anchor at bottom
+            l = other.left - tw + lp.leftMargin;
+            r = other.left - lp.rightMargin;
+            t = cy - th / 2 + lp.topMargin;
+            b = cy + th / 2 - lp.bottomMargin;
+            break;
+        case 90:
+            // phone landscape: below anchor on right
+            l = cx - tw / 2 + lp.leftMargin;
+            r = cx + tw / 2 - lp.rightMargin;
+            t = other.bottom + lp.topMargin;
+            b = other.bottom + th - lp.bottomMargin;
+            break;
+        case 180:
+            // phone upside down: right of anchor at top
+            l = other.right + lp.leftMargin;
+            r = other.right + tw - lp.rightMargin;
+            t = cy - th / 2 + lp.topMargin;
+            b = cy + th / 2 - lp.bottomMargin;
+            break;
+        case 270:
+            // reverse landscape: above anchor on left
+            l = cx - tw / 2 + lp.leftMargin;
+            r = cx + tw / 2 - lp.rightMargin;
+            t = other.top - th + lp.topMargin;
+            b = other.top - lp.bottomMargin;
+            break;
+        }
+        v.layout(l, t, r, b);
+    }
+
+    private void toRight(View v, Rect other, int rotation) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
+        int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+        int th = lp.topMargin + v.getMeasuredHeight() + lp.bottomMargin;
+        int cx = (other.left + other.right) / 2;
+        int cy = (other.top + other.bottom) / 2;
+        int l = 0, r = 0, t = 0, b = 0;
+        switch (rotation) {
+        case 0:
+            l = other.right + lp.leftMargin;
+            r = other.right + tw - lp.rightMargin;
+            t = cy - th / 2 + lp.topMargin;
+            b = cy + th / 2 - lp.bottomMargin;
+            break;
+        case 90:
+            l = cx - tw / 2 + lp.leftMargin;
+            r = cx + tw / 2 - lp.rightMargin;
+            t = other.top - th + lp.topMargin;
+            b = other.top - lp.bottomMargin;
+            break;
+        case 180:
+            l = other.left - tw + lp.leftMargin;
+            r = other.left - lp.rightMargin;
+            t = cy - th / 2 + lp.topMargin;
+            b = cy + th / 2 - lp.bottomMargin;
+            break;
+        case 270:
+            l = cx - tw / 2 + lp.leftMargin;
+            r = cx + tw / 2 - lp.rightMargin;
+            t = other.bottom + lp.topMargin;
+            b = other.bottom + th - lp.bottomMargin;
+            break;
+        }
+        v.layout(l, t, r, b);
+    }
+
+    private void topRight(View v, int l, int t, int r, int b) {
+        // layout using the specific margins; the rotation code messes up the others
+        int mt = getContext().getResources().getDimensionPixelSize(R.dimen.capture_margin_top);
+        int mr = getContext().getResources().getDimensionPixelSize(R.dimen.capture_margin_right);
+        v.layout(r - v.getMeasuredWidth() - mr, t + mt, r - mr, t + mt + v.getMeasuredHeight());
+    }
+
+    // In reverse landscape and reverse portrait, camera controls will be laid out
+    // on the wrong side of the screen. We need to make adjustment to move the controls
+    // to the USB side
+    public void adjustControlsToRightPosition() {
+        int orientation = getUnifiedRotation();
+        if (orientation >= 180) {
+            flipChildren();
+        }
+    }
+
+    private void adjustBackground() {
+        int rotation = getUnifiedRotation();
+        // remove current drawable and reset rotation
+        mBackgroundView.setBackgroundDrawable(null);
+        mBackgroundView.setRotationX(0);
+        mBackgroundView.setRotationY(0);
+        // if the switcher background is top aligned we need to flip the background
+        // drawable vertically; if left aligned, flip horizontally
+        switch (rotation) {
+            case 180:
+                mBackgroundView.setRotationX(180);
+                break;
+            case 270:
+                mBackgroundView.setRotationY(180);
+                break;
+            default:
+                break;
+        }
+        mBackgroundView.setBackgroundResource(R.drawable.switcher_bg);
+    }
+
+}
diff --git a/src/com/android/camera/ui/CameraRootView.java b/src/com/android/camera/ui/CameraRootView.java
new file mode 100644
index 0000000..76fea2c
--- /dev/null
+++ b/src/com/android/camera/ui/CameraRootView.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class CameraRootView extends RelativeLayout {
+
+    private int mTopMargin = 0;
+    private int mBottomMargin = 0;
+    private int mLeftMargin = 0;
+    private int mRightMargin = 0;
+    private int mOffset = 0;
+    private Rect mCurrentInsets;
+    public CameraRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // Layout the window as if we did not need navigation bar
+        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+    }
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        super.fitSystemWindows(insets);
+        mCurrentInsets = insets;
+        // insets include status bar, navigation bar, etc
+        // In this case, we are only concerned with the size of nav bar
+        if (mOffset > 0) return true;
+
+        if (insets.bottom > 0) {
+            mOffset = insets.bottom;
+        } else if (insets.right > 0) {
+            mOffset = insets.right;
+        }
+        return true;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int rotation = Util.getDisplayRotation((Activity) getContext());
+        // all the layout code assumes camera device orientation to be portrait
+        // adjust rotation for landscape
+        int orientation = getResources().getConfiguration().orientation;
+        int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT
+                : Configuration.ORIENTATION_LANDSCAPE;
+        if (camOrientation != orientation) {
+            rotation = (rotation + 90) % 360;
+        }
+        // calculate margins
+        mLeftMargin = 0;
+        mRightMargin = 0;
+        mBottomMargin = 0;
+        mTopMargin = 0;
+        switch (rotation) {
+            case 0:
+                mBottomMargin += mOffset;
+                break;
+            case 90:
+                mRightMargin += mOffset;
+                break;
+            case 180:
+                mTopMargin += mOffset;
+                break;
+            case 270:
+                mLeftMargin += mOffset;
+                break;
+        }
+        if (mCurrentInsets != null) {
+            if (mCurrentInsets.right > 0) {
+                // navigation bar on the right
+                mRightMargin = mRightMargin > 0 ? mRightMargin : mCurrentInsets.right;
+            } else {
+                // navigation bar on the bottom
+                mBottomMargin = mBottomMargin > 0 ? mBottomMargin : mCurrentInsets.bottom;
+            }
+        }
+        // make sure all the children are resized
+        super.onMeasure(widthMeasureSpec - mLeftMargin - mRightMargin,
+                heightMeasureSpec - mTopMargin - mBottomMargin);
+
+        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    @Override
+    public void onLayout(boolean changed, int l, int t, int r, int b) {
+        int orientation = getResources().getConfiguration().orientation;
+        // Lay out children
+        for (int i = 0; i < getChildCount(); i++) {
+            View v = getChildAt(i);
+            if (v instanceof CameraControls) {
+                // Lay out camera controls to center on the short side of the screen
+                // so that they stay in place during rotation
+                int width = v.getMeasuredWidth();
+                int height = v.getMeasuredHeight();
+                if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+                    int left = (l + r - width) / 2;
+                    v.layout(left, t + mTopMargin, left + width, b - mBottomMargin);
+                } else {
+                    int top = (t + b - height) / 2;
+                    v.layout(l + mLeftMargin, top, r - mRightMargin, top + height);
+                }
+            } else {
+                v.layout(l + mLeftMargin, t + mTopMargin, r - mRightMargin, b - mBottomMargin);
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java
new file mode 100644
index 0000000..537577f
--- /dev/null
+++ b/src/com/android/camera/ui/CameraSwitcher.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.LinearLayout;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.LightCycleHelper;
+import com.android.gallery3d.util.UsageStatistics;
+
+public class CameraSwitcher extends RotateImageView
+        implements OnClickListener, OnTouchListener {
+
+    private static final String TAG = "CAM_Switcher";
+    private static final int SWITCHER_POPUP_ANIM_DURATION = 200;
+
+    public static final int PHOTO_MODULE_INDEX = 0;
+    public static final int VIDEO_MODULE_INDEX = 1;
+    public static final int PANORAMA_MODULE_INDEX = 2;
+    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
+    private static final int[] DRAW_IDS = {
+            R.drawable.ic_switch_camera,
+            R.drawable.ic_switch_video,
+            R.drawable.ic_switch_pan,
+            R.drawable.ic_switch_photosphere
+    };
+    public interface CameraSwitchListener {
+        public void onCameraSelected(int i);
+        public void onShowSwitcherPopup();
+    }
+
+    private CameraSwitchListener mListener;
+    private int mCurrentIndex;
+    private int[] mModuleIds;
+    private int[] mDrawIds;
+    private int mItemSize;
+    private View mPopup;
+    private View mParent;
+    private boolean mShowingPopup;
+    private boolean mNeedsAnimationSetup;
+    private Drawable mIndicator;
+
+    private float mTranslationX = 0;
+    private float mTranslationY = 0;
+
+    private AnimatorListener mHideAnimationListener;
+    private AnimatorListener mShowAnimationListener;
+
+    public CameraSwitcher(Context context) {
+        super(context);
+        init(context);
+    }
+
+    public CameraSwitcher(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mItemSize = context.getResources().getDimensionPixelSize(R.dimen.switcher_size);
+        setOnClickListener(this);
+        mIndicator = context.getResources().getDrawable(R.drawable.ic_switcher_menu_indicator);
+        initializeDrawables(context);
+    }
+
+    public void initializeDrawables(Context context) {
+        int totaldrawid = (LightCycleHelper.hasLightCycleCapture(context)
+                ? DRAW_IDS.length : DRAW_IDS.length - 1);
+        if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
+
+        int[] drawids = new int[totaldrawid];
+        int[] moduleids = new int[totaldrawid];
+        int ix = 0;
+        for (int i = 0; i < DRAW_IDS.length; i++) {
+            if (i == PANORAMA_MODULE_INDEX && !ApiHelper.HAS_OLD_PANORAMA) {
+            continue; // not enabled, so don't add to UI
+            }
+            if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(context)) {
+            continue; // not enabled, so don't add to UI
+            }
+            moduleids[ix] = i;
+            drawids[ix++] = DRAW_IDS[i];
+        }
+        setIds(moduleids, drawids);
+    }
+
+    public void setIds(int[] moduleids, int[] drawids) {
+        mDrawIds = drawids;
+        mModuleIds = moduleids;
+    }
+
+    public void setCurrentIndex(int i) {
+        mCurrentIndex = i;
+        setImageResource(mDrawIds[i]);
+    }
+
+    public void setSwitchListener(CameraSwitchListener l) {
+        mListener = l;
+    }
+
+    @Override
+    public void onClick(View v) {
+        showSwitcher();
+        mListener.onShowSwitcherPopup();
+    }
+
+    private void onCameraSelected(int ix) {
+        hidePopup();
+        if ((ix != mCurrentIndex) && (mListener != null)) {
+            UsageStatistics.onEvent("CameraModeSwitch", null, null);
+            UsageStatistics.setPendingTransitionCause(
+                    UsageStatistics.TRANSITION_MENU_TAP);
+            setCurrentIndex(ix);
+            mListener.onCameraSelected(mModuleIds[ix]);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        mIndicator.setBounds(getDrawable().getBounds());
+        mIndicator.draw(canvas);
+    }
+
+    private void initPopup() {
+        mParent = LayoutInflater.from(getContext()).inflate(R.layout.switcher_popup,
+                (ViewGroup) getParent());
+        LinearLayout content = (LinearLayout) mParent.findViewById(R.id.content);
+        mPopup = content;
+        // Set the gravity of the popup, so that it shows up at the right position
+        // on screen
+        LayoutParams lp = ((LayoutParams) mPopup.getLayoutParams());
+        lp.gravity = ((LayoutParams) mParent.findViewById(R.id.camera_switcher)
+                .getLayoutParams()).gravity;
+        mPopup.setLayoutParams(lp);
+
+        mPopup.setVisibility(View.INVISIBLE);
+        mNeedsAnimationSetup = true;
+        for (int i = mDrawIds.length - 1; i >= 0; i--) {
+            RotateImageView item = new RotateImageView(getContext());
+            item.setImageResource(mDrawIds[i]);
+            item.setBackgroundResource(R.drawable.bg_pressed);
+            final int index = i;
+            item.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (showsPopup()) onCameraSelected(index);
+                }
+            });
+            switch (mDrawIds[i]) {
+                case R.drawable.ic_switch_camera:
+                    item.setContentDescription(getContext().getResources().getString(
+                            R.string.accessibility_switch_to_camera));
+                    break;
+                case R.drawable.ic_switch_video:
+                    item.setContentDescription(getContext().getResources().getString(
+                            R.string.accessibility_switch_to_video));
+                    break;
+                case R.drawable.ic_switch_pan:
+                    item.setContentDescription(getContext().getResources().getString(
+                            R.string.accessibility_switch_to_panorama));
+                    break;
+                case R.drawable.ic_switch_photosphere:
+                    item.setContentDescription(getContext().getResources().getString(
+                            R.string.accessibility_switch_to_new_panorama));
+                    break;
+                default:
+                    break;
+            }
+            content.addView(item, new LinearLayout.LayoutParams(mItemSize, mItemSize));
+        }
+        mPopup.measure(MeasureSpec.makeMeasureSpec(mParent.getWidth(), MeasureSpec.AT_MOST),
+                MeasureSpec.makeMeasureSpec(mParent.getHeight(), MeasureSpec.AT_MOST));
+    }
+
+    public boolean showsPopup() {
+        return mShowingPopup;
+    }
+
+    public boolean isInsidePopup(MotionEvent evt) {
+        if (!showsPopup()) return false;
+        int topLeft[] = new int[2];
+        mPopup.getLocationOnScreen(topLeft);
+        int left = topLeft[0];
+        int top = topLeft[1];
+        int bottom = top + mPopup.getHeight();
+        int right = left + mPopup.getWidth();
+        return evt.getX() >= left && evt.getX() < right
+                && evt.getY() >= top && evt.getY() < bottom;
+    }
+
+    private void hidePopup() {
+        mShowingPopup = false;
+        setVisibility(View.VISIBLE);
+        if (mPopup != null && !animateHidePopup()) {
+            mPopup.setVisibility(View.INVISIBLE);
+        }
+        mParent.setOnTouchListener(null);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        if (showsPopup()) {
+            ((ViewGroup) mParent).removeView(mPopup);
+            mPopup = null;
+            initPopup();
+            mPopup.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void showSwitcher() {
+        mShowingPopup = true;
+        if (mPopup == null) {
+            initPopup();
+        }
+        layoutPopup();
+        mPopup.setVisibility(View.VISIBLE);
+        if (!animateShowPopup()) {
+            setVisibility(View.INVISIBLE);
+        }
+        mParent.setOnTouchListener(this);
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        closePopup();
+        return true;
+    }
+
+    public void closePopup() {
+        if (showsPopup()) {
+            hidePopup();
+        }
+    }
+
+    @Override
+    public void setOrientation(int degree, boolean animate) {
+        super.setOrientation(degree, animate);
+        ViewGroup content = (ViewGroup) mPopup;
+        if (content == null) return;
+        for (int i = 0; i < content.getChildCount(); i++) {
+            RotateImageView iv = (RotateImageView) content.getChildAt(i);
+            iv.setOrientation(degree, animate);
+        }
+    }
+
+    private void layoutPopup() {
+        int orientation = Util.getDisplayRotation((Activity) getContext());
+        int w = mPopup.getMeasuredWidth();
+        int h = mPopup.getMeasuredHeight();
+        if (orientation == 0) {
+            mPopup.layout(getRight() - w, getBottom() - h, getRight(), getBottom());
+            mTranslationX = 0;
+            mTranslationY = h / 3;
+        } else if (orientation == 90) {
+            mTranslationX = w / 3;
+            mTranslationY = - h / 3;
+            mPopup.layout(getRight() - w, getTop(), getRight(), getTop() + h);
+        } else if (orientation == 180) {
+            mTranslationX = - w / 3;
+            mTranslationY = - h / 3;
+            mPopup.layout(getLeft(), getTop(), getLeft() + w, getTop() + h);
+        } else {
+            mTranslationX = - w / 3;
+            mTranslationY = h - getHeight();
+            mPopup.layout(getLeft(), getBottom() - h, getLeft() + w, getBottom());
+        }
+    }
+
+    @Override
+    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mPopup != null) {
+            layoutPopup();
+        }
+    }
+
+    private void popupAnimationSetup() {
+        if (!ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
+            return;
+        }
+        layoutPopup();
+        mPopup.setScaleX(0.3f);
+        mPopup.setScaleY(0.3f);
+        mPopup.setTranslationX(mTranslationX);
+        mPopup.setTranslationY(mTranslationY);
+        mNeedsAnimationSetup = false;
+    }
+
+    private boolean animateHidePopup() {
+        if (!ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
+            return false;
+        }
+        if (mHideAnimationListener == null) {
+            mHideAnimationListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Verify that we weren't canceled
+                    if (!showsPopup() && mPopup != null) {
+                        mPopup.setVisibility(View.INVISIBLE);
+                        ((ViewGroup) mParent).removeView(mPopup);
+                        mPopup = null;
+                    }
+                }
+            };
+        }
+        mPopup.animate()
+                .alpha(0f)
+                .scaleX(0.3f).scaleY(0.3f)
+                .translationX(mTranslationX)
+                .translationY(mTranslationY)
+                .setDuration(SWITCHER_POPUP_ANIM_DURATION)
+                .setListener(mHideAnimationListener);
+        animate().alpha(1f).setDuration(SWITCHER_POPUP_ANIM_DURATION)
+                .setListener(null);
+        return true;
+    }
+
+    private boolean animateShowPopup() {
+        if (!ApiHelper.HAS_VIEW_PROPERTY_ANIMATOR) {
+            return false;
+        }
+        if (mNeedsAnimationSetup) {
+            popupAnimationSetup();
+        }
+        if (mShowAnimationListener == null) {
+            mShowAnimationListener = new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // Verify that we weren't canceled
+                    if (showsPopup()) {
+                        setVisibility(View.INVISIBLE);
+                        // request layout to make sure popup is laid out correctly on ICS
+                        mPopup.requestLayout();
+                    }
+                }
+            };
+        }
+        mPopup.animate()
+                .alpha(1f)
+                .scaleX(1f).scaleY(1f)
+                .translationX(0)
+                .translationY(0)
+                .setDuration(SWITCHER_POPUP_ANIM_DURATION)
+                .setListener(null);
+        animate().alpha(0f).setDuration(SWITCHER_POPUP_ANIM_DURATION)
+                .setListener(mShowAnimationListener);
+        return true;
+    }
+}
diff --git a/src/com/android/camera/ui/CheckedLinearLayout.java b/src/com/android/camera/ui/CheckedLinearLayout.java
new file mode 100644
index 0000000..4e77504
--- /dev/null
+++ b/src/com/android/camera/ui/CheckedLinearLayout.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+import android.widget.LinearLayout;
+
+public class CheckedLinearLayout extends LinearLayout implements Checkable {
+    private static final int[] CHECKED_STATE_SET = {
+        android.R.attr.state_checked
+    };
+    private boolean mChecked;
+
+    public CheckedLinearLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mChecked;
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        if (mChecked != checked) {
+            mChecked = checked;
+            refreshDrawableState();
+        }
+    }
+
+    @Override
+    public void toggle() {
+        setChecked(!mChecked);
+    }
+
+    @Override
+    public int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+        if (mChecked) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+}
diff --git a/src/com/android/camera/ui/CountDownView.java b/src/com/android/camera/ui/CountDownView.java
new file mode 100644
index 0000000..907d335
--- /dev/null
+++ b/src/com/android/camera/ui/CountDownView.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import java.util.Locale;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+
+public class CountDownView extends FrameLayout {
+
+    private static final String TAG = "CAM_CountDownView";
+    private static final int SET_TIMER_TEXT = 1;
+    private TextView mRemainingSecondsView;
+    private int mRemainingSecs = 0;
+    private OnCountDownFinishedListener mListener;
+    private Animation mCountDownAnim;
+    private SoundPool mSoundPool;
+    private int mBeepTwice;
+    private int mBeepOnce;
+    private boolean mPlaySound;
+    private final Handler mHandler = new MainHandler();
+
+    public CountDownView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mCountDownAnim = AnimationUtils.loadAnimation(context, R.anim.count_down_exit);
+        // Load the beeps
+        mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
+        mBeepOnce = mSoundPool.load(context, R.raw.beep_once, 1);
+        mBeepTwice = mSoundPool.load(context, R.raw.beep_twice, 1);
+    }
+
+    public boolean isCountingDown() {
+        return mRemainingSecs > 0;
+    };
+
+    public interface OnCountDownFinishedListener {
+        public void onCountDownFinished();
+    }
+
+    private void remainingSecondsChanged(int newVal) {
+        mRemainingSecs = newVal;
+        if (newVal == 0) {
+            // Countdown has finished
+            setVisibility(View.INVISIBLE);
+            mListener.onCountDownFinished();
+        } else {
+            Locale locale = getResources().getConfiguration().locale;
+            String localizedValue = String.format(locale, "%d", newVal);
+            mRemainingSecondsView.setText(localizedValue);
+            // Fade-out animation
+            mCountDownAnim.reset();
+            mRemainingSecondsView.clearAnimation();
+            mRemainingSecondsView.startAnimation(mCountDownAnim);
+
+            // Play sound effect for the last 3 seconds of the countdown
+            if (mPlaySound) {
+                if (newVal == 1) {
+                    mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
+                } else if (newVal <= 3) {
+                    mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
+                }
+            }
+            // Schedule the next remainingSecondsChanged() call in 1 second
+            mHandler.sendEmptyMessageDelayed(SET_TIMER_TEXT, 1000);
+        }
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mRemainingSecondsView = (TextView) findViewById(R.id.remaining_seconds);
+    }
+
+    public void setCountDownFinishedListener(OnCountDownFinishedListener listener) {
+        mListener = listener;
+    }
+
+    public void startCountDown(int sec, boolean playSound) {
+        if (sec <= 0) {
+            Log.w(TAG, "Invalid input for countdown timer: " + sec + " seconds");
+            return;
+        }
+        setVisibility(View.VISIBLE);
+        mPlaySound = playSound;
+        remainingSecondsChanged(sec);
+    }
+
+    public void cancelCountDown() {
+        if (mRemainingSecs > 0) {
+            mRemainingSecs = 0;
+            mHandler.removeMessages(SET_TIMER_TEXT);
+            setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            if (message.what == SET_TIMER_TEXT) {
+                remainingSecondsChanged(mRemainingSecs -1);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/ui/CountdownTimerPopup.java b/src/com/android/camera/ui/CountdownTimerPopup.java
new file mode 100644
index 0000000..7c3572b
--- /dev/null
+++ b/src/com/android/camera/ui/CountdownTimerPopup.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.NumberPicker;
+import android.widget.NumberPicker.OnValueChangeListener;
+
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+import java.util.Locale;
+
+/**
+ * This is a popup window that allows users to specify a countdown timer
+ */
+
+public class CountdownTimerPopup extends AbstractSettingPopup {
+    private static final String TAG = "TimerSettingPopup";
+    private NumberPicker mNumberSpinner;
+    private String[] mDurations;
+    private ListPreference mTimer;
+    private ListPreference mBeep;
+    private Listener mListener;
+    private Button mConfirmButton;
+    private View mPickerTitle;
+    private CheckBox mTimerSound;
+    private View mSoundTitle;
+
+    static public interface Listener {
+        public void onListPrefChanged(ListPreference pref);
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public CountdownTimerPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void initialize(ListPreference timer, ListPreference beep) {
+        mTimer = timer;
+        mBeep = beep;
+        // Set title.
+        mTitle.setText(mTimer.getTitle());
+
+        // Duration
+        CharSequence[] entries = mTimer.getEntryValues();
+        mDurations = new String[entries.length];
+        Locale locale = getResources().getConfiguration().locale;
+        mDurations[0] = getResources().getString(R.string.setting_off); // Off
+        for (int i = 1; i < entries.length; i++)
+            mDurations[i] =  String.format(locale, "%d", Integer.parseInt(entries[i].toString()));
+        int durationCount = mDurations.length;
+        mNumberSpinner = (NumberPicker) findViewById(R.id.duration);
+        mNumberSpinner.setMinValue(0);
+        mNumberSpinner.setMaxValue(durationCount - 1);
+        mNumberSpinner.setDisplayedValues(mDurations);
+        mNumberSpinner.setWrapSelectorWheel(false);
+        mNumberSpinner.setOnValueChangedListener(new OnValueChangeListener() {
+            @Override
+            public void onValueChange(NumberPicker picker, int oldValue, int newValue) {
+                setTimeSelectionEnabled(newValue != 0);
+            }
+        });
+        mConfirmButton = (Button) findViewById(R.id.timer_set_button);
+        mPickerTitle = findViewById(R.id.set_time_interval_title);
+
+        // Disable focus on the spinners to prevent keyboard from coming up
+        mNumberSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
+
+        mConfirmButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                updateInputState();
+            }
+        });
+        mTimerSound = (CheckBox) findViewById(R.id.sound_check_box);
+        mSoundTitle = findViewById(R.id.beep_title);
+    }
+
+    private void restoreSetting() {
+        int index = mTimer.findIndexOfValue(mTimer.getValue());
+        if (index == -1) {
+            Log.e(TAG, "Invalid preference value.");
+            mTimer.print();
+            throw new IllegalArgumentException();
+        } else {
+            setTimeSelectionEnabled(index != 0);
+            mNumberSpinner.setValue(index);
+        }
+        boolean checked = mBeep.findIndexOfValue(mBeep.getValue()) != 0;
+        mTimerSound.setChecked(checked);
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        if (visibility == View.VISIBLE) {
+            if (getVisibility() != View.VISIBLE) {
+                // Set the number pickers and on/off switch to be consistent
+                // with the preference
+                restoreSetting();
+            }
+        }
+        super.setVisibility(visibility);
+    }
+
+    protected void setTimeSelectionEnabled(boolean enabled) {
+        mPickerTitle.setVisibility(enabled ? VISIBLE : INVISIBLE);
+        mTimerSound.setEnabled(enabled);
+        mSoundTitle.setEnabled(enabled);
+    }
+
+    @Override
+    public void reloadPreference() {
+    }
+
+    private void updateInputState() {
+        mTimer.setValueIndex(mNumberSpinner.getValue());
+        mBeep.setValueIndex(mTimerSound.isChecked() ? 1 : 0);
+        if (mListener != null) {
+            mListener.onListPrefChanged(mTimer);
+            mListener.onListPrefChanged(mBeep);
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/EffectSettingPopup.java b/src/com/android/camera/ui/EffectSettingPopup.java
new file mode 100644
index 0000000..568781a
--- /dev/null
+++ b/src/com/android/camera/ui/EffectSettingPopup.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.SimpleAdapter;
+
+import com.android.camera.IconListPreference;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+// A popup window that shows video effect setting. It has two grid view.
+// One shows the goofy face effects. The other shows the background replacer
+// effects.
+public class EffectSettingPopup extends AbstractSettingPopup implements
+        AdapterView.OnItemClickListener, View.OnClickListener {
+    private static final String TAG = "EffectSettingPopup";
+    private String mNoEffect;
+    private IconListPreference mPreference;
+    private Listener mListener;
+    private View mClearEffects;
+    private GridView mSillyFacesGrid;
+    private GridView mBackgroundGrid;
+
+    // Data for silly face items. (text, image, and preference value)
+    ArrayList<HashMap<String, Object>> mSillyFacesItem =
+            new ArrayList<HashMap<String, Object>>();
+
+    // Data for background replacer items. (text, image, and preference value)
+    ArrayList<HashMap<String, Object>> mBackgroundItem =
+            new ArrayList<HashMap<String, Object>>();
+
+
+    static public interface Listener {
+        public void onSettingChanged();
+    }
+
+    public EffectSettingPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mNoEffect = context.getString(R.string.pref_video_effect_default);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mClearEffects = findViewById(R.id.clear_effects);
+        mClearEffects.setOnClickListener(this);
+        mSillyFacesGrid = (GridView) findViewById(R.id.effect_silly_faces);
+        mBackgroundGrid = (GridView) findViewById(R.id.effect_background);
+    }
+
+    public void initialize(IconListPreference preference) {
+        mPreference = preference;
+        Context context = getContext();
+        CharSequence[] entries = mPreference.getEntries();
+        CharSequence[] entryValues = mPreference.getEntryValues();
+        int[] iconIds = mPreference.getImageIds();
+        if (iconIds == null) {
+            iconIds = mPreference.getLargeIconIds();
+        }
+
+        // Set title.
+        mTitle.setText(mPreference.getTitle());
+
+        for(int i = 0; i < entries.length; ++i) {
+            String value = entryValues[i].toString();
+            if (value.equals(mNoEffect)) continue;  // no effect, skip it.
+            HashMap<String, Object> map = new HashMap<String, Object>();
+            map.put("value", value);
+            map.put("text", entries[i].toString());
+            if (iconIds != null) map.put("image", iconIds[i]);
+            if (value.startsWith("goofy_face")) {
+                mSillyFacesItem.add(map);
+            } else if (value.startsWith("backdropper")) {
+                mBackgroundItem.add(map);
+            }
+        }
+
+        boolean hasSillyFaces = mSillyFacesItem.size() > 0;
+        boolean hasBackground = mBackgroundItem.size() > 0;
+
+        // Initialize goofy face if it is supported.
+        if (hasSillyFaces) {
+            findViewById(R.id.effect_silly_faces_title).setVisibility(View.VISIBLE);
+            findViewById(R.id.effect_silly_faces_title_separator).setVisibility(View.VISIBLE);
+            mSillyFacesGrid.setVisibility(View.VISIBLE);
+            SimpleAdapter sillyFacesItemAdapter = new SimpleAdapter(context,
+                    mSillyFacesItem, R.layout.effect_setting_item,
+                    new String[] {"text", "image"},
+                    new int[] {R.id.text, R.id.image});
+            mSillyFacesGrid.setAdapter(sillyFacesItemAdapter);
+            mSillyFacesGrid.setOnItemClickListener(this);
+        }
+
+        if (hasSillyFaces && hasBackground) {
+            findViewById(R.id.effect_background_separator).setVisibility(View.VISIBLE);
+        }
+
+        // Initialize background replacer if it is supported.
+        if (hasBackground) {
+            findViewById(R.id.effect_background_title).setVisibility(View.VISIBLE);
+            findViewById(R.id.effect_background_title_separator).setVisibility(View.VISIBLE);
+            mBackgroundGrid.setVisibility(View.VISIBLE);
+            SimpleAdapter backgroundItemAdapter = new SimpleAdapter(context,
+                    mBackgroundItem, R.layout.effect_setting_item,
+                    new String[] {"text", "image"},
+                    new int[] {R.id.text, R.id.image});
+            mBackgroundGrid.setAdapter(backgroundItemAdapter);
+            mBackgroundGrid.setOnItemClickListener(this);
+        }
+
+        reloadPreference();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        if (visibility == View.VISIBLE) {
+            if (getVisibility() != View.VISIBLE) {
+                // Do not show or hide "Clear effects" button when the popup
+                // is already visible. Otherwise it looks strange.
+                boolean noEffect = mPreference.getValue().equals(mNoEffect);
+                mClearEffects.setVisibility(noEffect ? View.GONE : View.VISIBLE);
+            }
+            reloadPreference();
+        }
+        super.setVisibility(visibility);
+    }
+
+    // The value of the preference may have changed. Update the UI.
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    @Override
+    public void reloadPreference() {
+        mBackgroundGrid.setItemChecked(mBackgroundGrid.getCheckedItemPosition(), false);
+        mSillyFacesGrid.setItemChecked(mSillyFacesGrid.getCheckedItemPosition(), false);
+
+        String value = mPreference.getValue();
+        if (value.equals(mNoEffect)) return;
+
+        for (int i = 0; i < mSillyFacesItem.size(); i++) {
+            if (value.equals(mSillyFacesItem.get(i).get("value"))) {
+                mSillyFacesGrid.setItemChecked(i, true);
+                return;
+            }
+        }
+
+        for (int i = 0; i < mBackgroundItem.size(); i++) {
+            if (value.equals(mBackgroundItem.get(i).get("value"))) {
+                mBackgroundGrid.setItemChecked(i, true);
+                return;
+            }
+        }
+
+        Log.e(TAG, "Invalid preference value: " + value);
+        mPreference.print();
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view,
+            int index, long id) {
+        String value;
+        if (parent == mSillyFacesGrid) {
+            value = (String) mSillyFacesItem.get(index).get("value");
+        } else if (parent == mBackgroundGrid) {
+            value = (String) mBackgroundItem.get(index).get("value");
+        } else {
+            return;
+        }
+
+        // Tapping the selected effect will deselect it (clear effects).
+        if (value.equals(mPreference.getValue())) {
+            mPreference.setValue(mNoEffect);
+        } else {
+            mPreference.setValue(value);
+        }
+        reloadPreference();
+        if (mListener != null) mListener.onSettingChanged();
+    }
+
+    @Override
+    public void onClick(View v) {
+        // Clear the effect.
+        mPreference.setValue(mNoEffect);
+        reloadPreference();
+        if (mListener != null) mListener.onSettingChanged();
+    }
+}
diff --git a/src/com/android/camera/ui/ExpandedGridView.java b/src/com/android/camera/ui/ExpandedGridView.java
new file mode 100644
index 0000000..13cf58f
--- /dev/null
+++ b/src/com/android/camera/ui/ExpandedGridView.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridView;
+
+public class ExpandedGridView extends GridView {
+    public ExpandedGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // If UNSPECIFIED is passed to GridView, it will show only one row.
+        // Here GridView is put in a ScrollView, so pass it a very big size with
+        // AT_MOST to show all the rows.
+        heightMeasureSpec = MeasureSpec.makeMeasureSpec(65536, MeasureSpec.AT_MOST);
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+}
diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java
new file mode 100644
index 0000000..f4dd823
--- /dev/null
+++ b/src/com/android/camera/ui/FaceView.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.hardware.Camera.Face;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.CameraScreenNail;
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+@TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class FaceView extends View
+    implements FocusIndicator, Rotatable {
+    private static final String TAG = "CAM FaceView";
+    private final boolean LOGV = false;
+    // The value for android.hardware.Camera.setDisplayOrientation.
+    private int mDisplayOrientation;
+    // The orientation compensation for the face indicator to make it look
+    // correctly in all device orientations. Ex: if the value is 90, the
+    // indicator should be rotated 90 degrees counter-clockwise.
+    private int mOrientation;
+    private boolean mMirror;
+    private boolean mPause;
+    private Matrix mMatrix = new Matrix();
+    private RectF mRect = new RectF();
+    // As face detection can be flaky, we add a layer of filtering on top of it
+    // to avoid rapid changes in state (eg, flickering between has faces and
+    // not having faces)
+    private Face[] mFaces;
+    private Face[] mPendingFaces;
+    private int mColor;
+    private final int mFocusingColor;
+    private final int mFocusedColor;
+    private final int mFailColor;
+    private Paint mPaint;
+    private volatile boolean mBlocked;
+
+    private int mUncroppedWidth;
+    private int mUncroppedHeight;
+    private static final int MSG_SWITCH_FACES = 1;
+    private static final int SWITCH_DELAY = 70;
+    private boolean mStateSwitchPending = false;
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MSG_SWITCH_FACES:
+                mStateSwitchPending = false;
+                mFaces = mPendingFaces;
+                invalidate();
+                break;
+            }
+        }
+    };
+
+    public FaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        Resources res = getResources();
+        mFocusingColor = res.getColor(R.color.face_detect_start);
+        mFocusedColor = res.getColor(R.color.face_detect_success);
+        mFailColor = res.getColor(R.color.face_detect_fail);
+        mColor = mFocusingColor;
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setStyle(Style.STROKE);
+        mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
+    }
+
+    public void setFaces(Face[] faces) {
+        if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
+        if (mPause) return;
+        if (mFaces != null) {
+            if ((faces.length > 0 && mFaces.length == 0)
+                    || (faces.length == 0 && mFaces.length > 0)) {
+                mPendingFaces = faces;
+                if (!mStateSwitchPending) {
+                    mStateSwitchPending = true;
+                    mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY);
+                }
+                return;
+            }
+        }
+        if (mStateSwitchPending) {
+            mStateSwitchPending = false;
+            mHandler.removeMessages(MSG_SWITCH_FACES);
+        }
+        mFaces = faces;
+        invalidate();
+    }
+
+    public void setDisplayOrientation(int orientation) {
+        mDisplayOrientation = orientation;
+        if (LOGV) Log.v(TAG, "mDisplayOrientation=" + orientation);
+    }
+
+    @Override
+    public void setOrientation(int orientation, boolean animation) {
+        mOrientation = orientation;
+        invalidate();
+    }
+
+    public void setMirror(boolean mirror) {
+        mMirror = mirror;
+        if (LOGV) Log.v(TAG, "mMirror=" + mirror);
+    }
+
+    public boolean faceExists() {
+        return (mFaces != null && mFaces.length > 0);
+    }
+
+    @Override
+    public void showStart() {
+        mColor = mFocusingColor;
+        invalidate();
+    }
+
+    // Ignore the parameter. No autofocus animation for face detection.
+    @Override
+    public void showSuccess(boolean timeout) {
+        mColor = mFocusedColor;
+        invalidate();
+    }
+
+    // Ignore the parameter. No autofocus animation for face detection.
+    @Override
+    public void showFail(boolean timeout) {
+        mColor = mFailColor;
+        invalidate();
+    }
+
+    @Override
+    public void clear() {
+        // Face indicator is displayed during preview. Do not clear the
+        // drawable.
+        mColor = mFocusingColor;
+        mFaces = null;
+        invalidate();
+    }
+
+    public void pause() {
+        mPause = true;
+    }
+
+    public void resume() {
+        mPause = false;
+    }
+
+    public void setBlockDraw(boolean block) {
+        mBlocked = block;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) {
+            int rw, rh;
+            if (mUncroppedWidth == 0) {
+                // TODO: This check is temporary. It needs to be removed after the
+                // refactoring is fully functioning.
+                final CameraScreenNail sn = ((CameraActivity) getContext()).getCameraScreenNail();
+                rw = sn.getUncroppedRenderWidth();
+                rh = sn.getUncroppedRenderHeight();
+            } else {
+                rw = mUncroppedWidth;
+                rh = mUncroppedHeight;
+            }
+            // Prepare the matrix.
+            if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180)))
+                    || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) {
+                int temp = rw;
+                rw = rh;
+                rh = temp;
+            }
+            Util.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, rw, rh);
+            int dx = (getWidth() - rw) / 2;
+            int dy = (getHeight() - rh) / 2;
+
+            // Focus indicator is directional. Rotate the matrix and the canvas
+            // so it looks correctly in all orientations.
+            canvas.save();
+            mMatrix.postRotate(mOrientation); // postRotate is clockwise
+            canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas)
+            for (int i = 0; i < mFaces.length; i++) {
+                // Filter out false positives.
+                if (mFaces[i].score < 50) continue;
+
+                // Transform the coordinates.
+                mRect.set(mFaces[i].rect);
+                if (LOGV) Util.dumpRect(mRect, "Original rect");
+                mMatrix.mapRect(mRect);
+                if (LOGV) Util.dumpRect(mRect, "Transformed rect");
+                mPaint.setColor(mColor);
+                mRect.offset(dx, dy);
+                canvas.drawOval(mRect, mPaint);
+            }
+            canvas.restore();
+        }
+        super.onDraw(canvas);
+    }
+}
diff --git a/src/com/android/camera/ui/FocusIndicator.java b/src/com/android/camera/ui/FocusIndicator.java
new file mode 100644
index 0000000..e060570
--- /dev/null
+++ b/src/com/android/camera/ui/FocusIndicator.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+public interface FocusIndicator {
+    public void showStart();
+    public void showSuccess(boolean timeout);
+    public void showFail(boolean timeout);
+    public void clear();
+}
diff --git a/src/com/android/camera/ui/InLineSettingCheckBox.java b/src/com/android/camera/ui/InLineSettingCheckBox.java
new file mode 100644
index 0000000..c1aa5a9
--- /dev/null
+++ b/src/com/android/camera/ui/InLineSettingCheckBox.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+
+
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+/* A check box setting control which turns on/off the setting. */
+public class InLineSettingCheckBox extends InLineSettingItem {
+    private CheckBox mCheckBox;
+
+    OnCheckedChangeListener mCheckedChangeListener = new OnCheckedChangeListener() {
+        @Override
+        public void onCheckedChanged(CompoundButton buttonView, boolean desiredState) {
+            changeIndex(desiredState ? 1 : 0);
+        }
+    };
+
+    public InLineSettingCheckBox(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mCheckBox = (CheckBox) findViewById(R.id.setting_check_box);
+        mCheckBox.setOnCheckedChangeListener(mCheckedChangeListener);
+    }
+
+    @Override
+    public void initialize(ListPreference preference) {
+        super.initialize(preference);
+        // Add content descriptions for the increment and decrement buttons.
+        mCheckBox.setContentDescription(getContext().getResources().getString(
+                R.string.accessibility_check_box, mPreference.getTitle()));
+    }
+
+    @Override
+    protected void updateView() {
+        mCheckBox.setOnCheckedChangeListener(null);
+        if (mOverrideValue == null) {
+            mCheckBox.setChecked(mIndex == 1);
+        } else {
+            int index = mPreference.findIndexOfValue(mOverrideValue);
+            mCheckBox.setChecked(index == 1);
+        }
+        mCheckBox.setOnCheckedChangeListener(mCheckedChangeListener);
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        event.getText().add(mPreference.getTitle());
+        return true;
+    }
+
+    @Override
+    public void setEnabled(boolean enable) {
+        if (mTitle != null) mTitle.setEnabled(enable);
+        if (mCheckBox != null) mCheckBox.setEnabled(enable);
+    }
+}
diff --git a/src/com/android/camera/ui/InLineSettingItem.java b/src/com/android/camera/ui/InLineSettingItem.java
new file mode 100644
index 0000000..839a77f
--- /dev/null
+++ b/src/com/android/camera/ui/InLineSettingItem.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+/**
+ * A one-line camera setting could be one of three types: knob, switch or restore
+ * preference button. The setting includes a title for showing the preference
+ * title which is initialized in the SimpleAdapter. A knob also includes
+ * (ex: Picture size), a previous button, the current value (ex: 5MP),
+ * and a next button. A switch, i.e. the preference RecordLocationPreference,
+ * has only two values on and off which will be controlled in a switch button.
+ * Other setting popup window includes several InLineSettingItem items with
+ * different types if possible.
+ */
+public abstract class InLineSettingItem extends LinearLayout {
+    private Listener mListener;
+    protected ListPreference mPreference;
+    protected int mIndex;
+    // Scene mode can override the original preference value.
+    protected String mOverrideValue;
+    protected TextView mTitle;
+
+    static public interface Listener {
+        public void onSettingChanged(ListPreference pref);
+    }
+
+    public InLineSettingItem(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected void setTitle(ListPreference preference) {
+        mTitle = ((TextView) findViewById(R.id.title));
+        mTitle.setText(preference.getTitle());
+    }
+
+    public void initialize(ListPreference preference) {
+        setTitle(preference);
+        if (preference == null) return;
+        mPreference = preference;
+        reloadPreference();
+    }
+
+    protected abstract void updateView();
+
+    protected boolean changeIndex(int index) {
+        if (index >= mPreference.getEntryValues().length || index < 0) return false;
+        mIndex = index;
+        mPreference.setValueIndex(mIndex);
+        if (mListener != null) {
+            mListener.onSettingChanged(mPreference);
+        }
+        updateView();
+        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+        return true;
+    }
+
+    // The value of the preference may have changed. Update the UI.
+    public void reloadPreference() {
+        mIndex = mPreference.findIndexOfValue(mPreference.getValue());
+        updateView();
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public void overrideSettings(String value) {
+        mOverrideValue = value;
+        updateView();
+    }
+}
diff --git a/src/com/android/camera/ui/InLineSettingMenu.java b/src/com/android/camera/ui/InLineSettingMenu.java
new file mode 100644
index 0000000..8e45c3e
--- /dev/null
+++ b/src/com/android/camera/ui/InLineSettingMenu.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.TextView;
+
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+/* Setting menu item that will bring up a menu when you click on it. */
+public class InLineSettingMenu extends InLineSettingItem {
+    private static final String TAG = "InLineSettingMenu";
+    // The view that shows the current selected setting. Ex: 5MP
+    private TextView mEntry;
+
+    public InLineSettingMenu(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mEntry = (TextView) findViewById(R.id.current_setting);
+    }
+
+    @Override
+    public void initialize(ListPreference preference) {
+        super.initialize(preference);
+        //TODO: add contentDescription
+    }
+
+    @Override
+    protected void updateView() {
+        if (mOverrideValue == null) {
+            mEntry.setText(mPreference.getEntry());
+        } else {
+            int index = mPreference.findIndexOfValue(mOverrideValue);
+            if (index != -1) {
+                mEntry.setText(mPreference.getEntries()[index]);
+            } else {
+                // Avoid the crash if camera driver has bugs.
+                Log.e(TAG, "Fail to find override value=" + mOverrideValue);
+                mPreference.print();
+            }
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        event.getText().add(mPreference.getTitle() + mPreference.getEntry());
+        return true;
+    }
+
+    @Override
+    public void setEnabled(boolean enable) {
+        super.setEnabled(enable);
+        if (mTitle != null) mTitle.setEnabled(enable);
+        if (mEntry != null) mEntry.setEnabled(enable);
+    }
+}
diff --git a/src/com/android/camera/ui/LayoutChangeHelper.java b/src/com/android/camera/ui/LayoutChangeHelper.java
new file mode 100644
index 0000000..ef4eb6a
--- /dev/null
+++ b/src/com/android/camera/ui/LayoutChangeHelper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.view.View;
+
+public class LayoutChangeHelper implements LayoutChangeNotifier {
+    private LayoutChangeNotifier.Listener mListener;
+    private boolean mFirstTimeLayout;
+    private View mView;
+
+    public LayoutChangeHelper(View v) {
+        mView = v;
+        mFirstTimeLayout = true;
+    }
+
+    @Override
+    public void setOnLayoutChangeListener(LayoutChangeNotifier.Listener listener) {
+        mListener = listener;
+    }
+
+    public void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (mListener == null) return;
+        if (mFirstTimeLayout || changed) {
+            mFirstTimeLayout = false;
+            mListener.onLayoutChange(mView, l, t, r, b);
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/LayoutChangeNotifier.java b/src/com/android/camera/ui/LayoutChangeNotifier.java
new file mode 100644
index 0000000..6261d34
--- /dev/null
+++ b/src/com/android/camera/ui/LayoutChangeNotifier.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.view.View;
+
+public interface LayoutChangeNotifier {
+    public interface Listener {
+        // Invoked only when the layout has changed or it is the first layout.
+        public void onLayoutChange(View v, int l, int t, int r, int b);
+    }
+
+    public void setOnLayoutChangeListener(LayoutChangeNotifier.Listener listener);
+}
diff --git a/src/com/android/camera/ui/LayoutNotifyView.java b/src/com/android/camera/ui/LayoutNotifyView.java
new file mode 100644
index 0000000..6e118fc
--- /dev/null
+++ b/src/com/android/camera/ui/LayoutNotifyView.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+/*
+ * Customized view to support onLayoutChange() at or before API 10.
+ */
+public class LayoutNotifyView extends View implements LayoutChangeNotifier {
+    private LayoutChangeHelper mLayoutChangeHelper = new LayoutChangeHelper(this);
+
+    public LayoutNotifyView(Context context) {
+        super(context);
+    }
+
+    public LayoutNotifyView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void setOnLayoutChangeListener(
+            LayoutChangeNotifier.Listener listener) {
+        mLayoutChangeHelper.setOnLayoutChangeListener(listener);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        mLayoutChangeHelper.onLayout(changed, l, t, r, b);
+    }
+}
diff --git a/src/com/android/camera/ui/ListPrefSettingPopup.java b/src/com/android/camera/ui/ListPrefSettingPopup.java
new file mode 100644
index 0000000..cfef73f
--- /dev/null
+++ b/src/com/android/camera/ui/ListPrefSettingPopup.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.SimpleAdapter;
+
+import com.android.camera.IconListPreference;
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// A popup window that shows one camera setting. The title is the name of the
+// setting (ex: white-balance). The entries are the supported values (ex:
+// daylight, incandescent, etc). If initialized with an IconListPreference,
+// the entries will contain both text and icons. Otherwise, entries will be
+// shown in text.
+public class ListPrefSettingPopup extends AbstractSettingPopup implements
+        AdapterView.OnItemClickListener {
+    private static final String TAG = "ListPrefSettingPopup";
+    private ListPreference mPreference;
+    private Listener mListener;
+
+    static public interface Listener {
+        public void onListPrefChanged(ListPreference pref);
+    }
+
+    public ListPrefSettingPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private class ListPrefSettingAdapter extends SimpleAdapter {
+        ListPrefSettingAdapter(Context context, List<? extends Map<String, ?>> data,
+                int resource, String[] from, int[] to) {
+            super(context, data, resource, from, to);
+        }
+
+        @Override
+        public void setViewImage(ImageView v, String value) {
+            if ("".equals(value)) {
+                // Some settings have no icons. Ex: exposure compensation.
+                v.setVisibility(View.GONE);
+            } else {
+                super.setViewImage(v, value);
+            }
+        }
+    }
+
+    public void initialize(ListPreference preference) {
+        mPreference = preference;
+        Context context = getContext();
+        CharSequence[] entries = mPreference.getEntries();
+        int[] iconIds = null;
+        if (preference instanceof IconListPreference) {
+            iconIds = ((IconListPreference) mPreference).getImageIds();
+            if (iconIds == null) {
+                iconIds = ((IconListPreference) mPreference).getLargeIconIds();
+            }
+        }
+        // Set title.
+        mTitle.setText(mPreference.getTitle());
+
+        // Prepare the ListView.
+        ArrayList<HashMap<String, Object>> listItem =
+                new ArrayList<HashMap<String, Object>>();
+        for(int i = 0; i < entries.length; ++i) {
+            HashMap<String, Object> map = new HashMap<String, Object>();
+            map.put("text", entries[i].toString());
+            if (iconIds != null) map.put("image", iconIds[i]);
+            listItem.add(map);
+        }
+        SimpleAdapter listItemAdapter = new ListPrefSettingAdapter(context, listItem,
+                R.layout.setting_item,
+                new String[] {"text", "image"},
+                new int[] {R.id.text, R.id.image});
+        ((ListView) mSettingList).setAdapter(listItemAdapter);
+        ((ListView) mSettingList).setOnItemClickListener(this);
+        reloadPreference();
+    }
+
+    // The value of the preference may have changed. Update the UI.
+    @Override
+    public void reloadPreference() {
+        int index = mPreference.findIndexOfValue(mPreference.getValue());
+        if (index != -1) {
+            ((ListView) mSettingList).setItemChecked(index, true);
+        } else {
+            Log.e(TAG, "Invalid preference value.");
+            mPreference.print();
+        }
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view,
+            int index, long id) {
+        mPreference.setValueIndex(index);
+        if (mListener != null) mListener.onListPrefChanged(mPreference);
+    }
+}
diff --git a/src/com/android/camera/ui/MoreSettingPopup.java b/src/com/android/camera/ui/MoreSettingPopup.java
new file mode 100644
index 0000000..5900058
--- /dev/null
+++ b/src/com/android/camera/ui/MoreSettingPopup.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.camera.ListPreference;
+import com.android.camera.PreferenceGroup;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+/* A popup window that contains several camera settings. */
+public class MoreSettingPopup extends AbstractSettingPopup
+        implements InLineSettingItem.Listener,
+        AdapterView.OnItemClickListener {
+    @SuppressWarnings("unused")
+    private static final String TAG = "MoreSettingPopup";
+
+    private Listener mListener;
+    private ArrayList<ListPreference> mListItem = new ArrayList<ListPreference>();
+
+    // Keep track of which setting items are disabled
+    // e.g. White balance will be disabled when scene mode is set to non-auto
+    private boolean[] mEnabled;
+
+    static public interface Listener {
+        public void onSettingChanged(ListPreference pref);
+        public void onPreferenceClicked(ListPreference pref);
+    }
+
+    private class MoreSettingAdapter extends ArrayAdapter<ListPreference> {
+        LayoutInflater mInflater;
+        String mOnString;
+        String mOffString;
+        MoreSettingAdapter() {
+            super(MoreSettingPopup.this.getContext(), 0, mListItem);
+            Context context = getContext();
+            mInflater = LayoutInflater.from(context);
+            mOnString = context.getString(R.string.setting_on);
+            mOffString = context.getString(R.string.setting_off);
+        }
+
+        private int getSettingLayoutId(ListPreference pref) {
+
+            if (isOnOffPreference(pref)) {
+                return R.layout.in_line_setting_check_box;
+            }
+            return R.layout.in_line_setting_menu;
+        }
+
+        private boolean isOnOffPreference(ListPreference pref) {
+            CharSequence[] entries = pref.getEntries();
+            if (entries.length != 2) return false;
+            String str1 = entries[0].toString();
+            String str2 = entries[1].toString();
+            return ((str1.equals(mOnString) && str2.equals(mOffString)) ||
+                    (str1.equals(mOffString) && str2.equals(mOnString)));
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView != null) return convertView;
+
+            ListPreference pref = mListItem.get(position);
+
+            int viewLayoutId = getSettingLayoutId(pref);
+            InLineSettingItem view = (InLineSettingItem)
+                    mInflater.inflate(viewLayoutId, parent, false);
+
+            view.initialize(pref); // no init for restore one
+            view.setSettingChangedListener(MoreSettingPopup.this);
+            if (position >= 0 && position < mEnabled.length) {
+                view.setEnabled(mEnabled[position]);
+            } else {
+                Log.w(TAG, "Invalid input: enabled list length, " + mEnabled.length
+                        + " position " + position);
+            }
+            return view;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            if (position >= 0 && position < mEnabled.length) {
+                return mEnabled[position];
+            }
+            return true;
+        }
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public MoreSettingPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void initialize(PreferenceGroup group, String[] keys) {
+        // Prepare the setting items.
+        for (int i = 0; i < keys.length; ++i) {
+            ListPreference pref = group.findPreference(keys[i]);
+            if (pref != null) mListItem.add(pref);
+        }
+
+        ArrayAdapter<ListPreference> mListItemAdapter = new MoreSettingAdapter();
+        ((ListView) mSettingList).setAdapter(mListItemAdapter);
+        ((ListView) mSettingList).setOnItemClickListener(this);
+        ((ListView) mSettingList).setSelector(android.R.color.transparent);
+        // Initialize mEnabled
+        mEnabled = new boolean[mListItem.size()];
+        for (int i = 0; i < mEnabled.length; i++) {
+            mEnabled[i] = true;
+        }
+    }
+
+    // When preferences are disabled, we will display them grayed out. Users
+    // will not be able to change the disabled preferences, but they can still see
+    // the current value of the preferences
+    public void setPreferenceEnabled(String key, boolean enable) {
+        int count = mEnabled == null ? 0 : mEnabled.length;
+        for (int j = 0; j < count; j++) {
+            ListPreference pref = mListItem.get(j);
+            if (pref != null && key.equals(pref.getKey())) {
+                mEnabled[j] = enable;
+                break;
+            }
+        }
+    }
+
+    public void onSettingChanged(ListPreference pref) {
+        if (mListener != null) {
+            mListener.onSettingChanged(pref);
+        }
+    }
+
+    // Scene mode can override other camera settings (ex: flash mode).
+    public void overrideSettings(final String ... keyvalues) {
+        int count = mEnabled == null ? 0 : mEnabled.length;
+        for (int i = 0; i < keyvalues.length; i += 2) {
+            String key = keyvalues[i];
+            String value = keyvalues[i + 1];
+            for (int j = 0; j < count; j++) {
+                ListPreference pref = mListItem.get(j);
+                if (pref != null && key.equals(pref.getKey())) {
+                    // Change preference
+                    if (value != null) pref.setValue(value);
+                    // If the preference is overridden, disable the preference
+                    boolean enable = value == null;
+                    mEnabled[j] = enable;
+                    if (mSettingList.getChildCount() > j) {
+                        mSettingList.getChildAt(j).setEnabled(enable);
+                    }
+                }
+            }
+        }
+        reloadPreference();
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position,
+            long id) {
+        if (mListener != null) {
+            ListPreference pref = mListItem.get(position);
+            mListener.onPreferenceClicked(pref);
+        }
+    }
+
+    @Override
+    public void reloadPreference() {
+        int count = mSettingList.getChildCount();
+        for (int i = 0; i < count; i++) {
+            ListPreference pref = mListItem.get(i);
+            if (pref != null) {
+                InLineSettingItem settingItem =
+                        (InLineSettingItem) mSettingList.getChildAt(i);
+                settingItem.reloadPreference();
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/OnIndicatorEventListener.java b/src/com/android/camera/ui/OnIndicatorEventListener.java
new file mode 100644
index 0000000..566f5c7
--- /dev/null
+++ b/src/com/android/camera/ui/OnIndicatorEventListener.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+public interface OnIndicatorEventListener {
+    public static int EVENT_ENTER_SECOND_LEVEL_INDICATOR_BAR = 0;
+    public static int EVENT_LEAVE_SECOND_LEVEL_INDICATOR_BAR = 1;
+    public static int EVENT_ENTER_ZOOM_CONTROL = 2;
+    public static int EVENT_LEAVE_ZOOM_CONTROL = 3;
+    void onIndicatorEvent(int event);
+}
diff --git a/src/com/android/camera/ui/OverlayRenderer.java b/src/com/android/camera/ui/OverlayRenderer.java
new file mode 100644
index 0000000..417e219
--- /dev/null
+++ b/src/com/android/camera/ui/OverlayRenderer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.view.MotionEvent;
+
+public abstract class OverlayRenderer implements RenderOverlay.Renderer {
+
+    private static final String TAG = "CAM OverlayRenderer";
+    protected RenderOverlay mOverlay;
+
+    protected int mLeft, mTop, mRight, mBottom;
+
+    protected boolean mVisible;
+
+    public void setVisible(boolean vis) {
+        mVisible = vis;
+        update();
+    }
+
+    public boolean isVisible() {
+        return mVisible;
+    }
+
+    // default does not handle touch
+    @Override
+    public boolean handlesTouch() {
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent evt) {
+        return false;
+    }
+
+    public abstract void onDraw(Canvas canvas);
+
+    public void draw(Canvas canvas) {
+        if (mVisible) {
+            onDraw(canvas);
+        }
+    }
+
+    @Override
+    public void setOverlay(RenderOverlay overlay) {
+        mOverlay = overlay;
+    }
+
+    @Override
+    public void layout(int left, int top, int right, int bottom) {
+        mLeft = left;
+        mRight = right;
+        mTop = top;
+        mBottom = bottom;
+    }
+
+    protected Context getContext() {
+        if (mOverlay != null) {
+            return mOverlay.getContext();
+        } else {
+            return null;
+        }
+    }
+
+    public int getWidth() {
+        return mRight - mLeft;
+    }
+
+    public int getHeight() {
+        return mBottom - mTop;
+    }
+
+    protected void update() {
+        if (mOverlay != null) {
+            mOverlay.update();
+        }
+    }
+
+}
diff --git a/src/com/android/camera/ui/PieItem.java b/src/com/android/camera/ui/PieItem.java
new file mode 100644
index 0000000..47fe067
--- /dev/null
+++ b/src/com/android/camera/ui/PieItem.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pie menu item
+ */
+public class PieItem {
+
+    public static interface OnClickListener {
+        void onClick(PieItem item);
+    }
+
+    private Drawable mDrawable;
+    private int level;
+
+    private boolean mSelected;
+    private boolean mEnabled;
+    private List<PieItem> mItems;
+    private Path mPath;
+    private OnClickListener mOnClickListener;
+    private float mAlpha;
+    private CharSequence mLabel;
+
+    // Gray out the view when disabled
+    private static final float ENABLED_ALPHA = 1;
+    private static final float DISABLED_ALPHA = (float) 0.3;
+    private boolean mChangeAlphaWhenDisabled = true;
+
+    public PieItem(Drawable drawable, int level) {
+        mDrawable = drawable;
+        this.level = level;
+        if (drawable != null) {
+            setAlpha(1f);
+        }
+        mEnabled = true;
+    }
+
+    public void setLabel(CharSequence txt) {
+        mLabel = txt;
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public boolean hasItems() {
+        return mItems != null;
+    }
+
+    public List<PieItem> getItems() {
+        return mItems;
+    }
+
+    public void addItem(PieItem item) {
+        if (mItems == null) {
+            mItems = new ArrayList<PieItem>();
+        }
+        mItems.add(item);
+    }
+
+    public void clearItems() {
+        mItems = null;
+    }
+
+    public void setLevel(int level) {
+        this.level = level;
+    }
+
+    public void setPath(Path p) {
+        mPath = p;
+    }
+
+    public Path getPath() {
+        return mPath;
+    }
+
+    public void setChangeAlphaWhenDisabled (boolean enable) {
+        mChangeAlphaWhenDisabled = enable;
+    }
+
+    public void setAlpha(float alpha) {
+        mAlpha = alpha;
+        mDrawable.setAlpha((int) (255 * alpha));
+    }
+
+    public void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+        if (mChangeAlphaWhenDisabled) {
+            if (mEnabled) {
+                setAlpha(ENABLED_ALPHA);
+            } else {
+                setAlpha(DISABLED_ALPHA);
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public void setSelected(boolean s) {
+        mSelected = s;
+    }
+
+    public boolean isSelected() {
+        return mSelected;
+    }
+
+    public int getLevel() {
+        return level;
+    }
+
+
+    public void setOnClickListener(OnClickListener listener) {
+        mOnClickListener = listener;
+    }
+
+    public void performClick() {
+        if (mOnClickListener != null) {
+            mOnClickListener.onClick(this);
+        }
+    }
+
+    public int getIntrinsicWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    public int getIntrinsicHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    public void setBounds(int left, int top, int right, int bottom) {
+        mDrawable.setBounds(left, top, right, bottom);
+    }
+
+    public void draw(Canvas canvas) {
+        mDrawable.draw(canvas);
+    }
+
+    public void setImageResource(Context context, int resId) {
+        Drawable d = context.getResources().getDrawable(resId).mutate();
+        d.setBounds(mDrawable.getBounds());
+        mDrawable = d;
+        setAlpha(mAlpha);
+    }
+
+}
diff --git a/src/com/android/camera/ui/PieMenuButton.java b/src/com/android/camera/ui/PieMenuButton.java
new file mode 100644
index 0000000..e571931
--- /dev/null
+++ b/src/com/android/camera/ui/PieMenuButton.java
@@ -0,0 +1,61 @@
+/*
+ * 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+public class PieMenuButton extends View {
+    private boolean mPressed;
+    private boolean mReadyToClick = false;
+    public PieMenuButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+        mPressed = isPressed();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (MotionEvent.ACTION_UP == event.getAction() && mPressed) {
+            // Perform a customized click as soon as the ACTION_UP event
+            // is received. The reason for doing this is that Framework
+            // delays the performClick() call after ACTION_UP. But we do not
+            // want the delay because it affects an important state change
+            // for PieRenderer.
+            mReadyToClick = true;
+            performClick();
+        }
+        return super.onTouchEvent(event);
+    }
+
+    @Override
+    public boolean performClick() {
+        if (mReadyToClick) {
+            // We only respond to our customized click which happens right
+            // after ACTION_UP event is received, with no delay.
+            mReadyToClick = false;
+            return super.performClick();
+        }
+        return false;
+    }
+};
\ No newline at end of file
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
new file mode 100644
index 0000000..edae2be
--- /dev/null
+++ b/src/com/android/camera/ui/PieRenderer.java
@@ -0,0 +1,1104 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Message;
+import android.util.FloatMath;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.Transformation;
+
+import com.android.camera.drawable.TextDrawable;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PieRenderer extends OverlayRenderer
+        implements FocusIndicator {
+
+    private static final String TAG = "CAM Pie";
+
+    // Sometimes continuous autofocus starts and stops several times quickly.
+    // These states are used to make sure the animation is run for at least some
+    // time.
+    private volatile int mState;
+    private ScaleAnimation mAnimation = new ScaleAnimation();
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_FOCUSING = 1;
+    private static final int STATE_FINISHING = 2;
+    private static final int STATE_PIE = 8;
+
+    private static final float MATH_PI_2 = (float)(Math.PI / 2);
+
+    private Runnable mDisappear = new Disappear();
+    private Animation.AnimationListener mEndAction = new EndAction();
+    private static final int SCALING_UP_TIME = 600;
+    private static final int SCALING_DOWN_TIME = 100;
+    private static final int DISAPPEAR_TIMEOUT = 200;
+    private static final int DIAL_HORIZONTAL = 157;
+    // fade out timings
+    private static final int PIE_FADE_OUT_DURATION = 600;
+
+    private static final long PIE_FADE_IN_DURATION = 200;
+    private static final long PIE_XFADE_DURATION = 200;
+    private static final long PIE_SELECT_FADE_DURATION = 300;
+    private static final long PIE_OPEN_SUB_DELAY = 400;
+    private static final long PIE_SLICE_DURATION = 80;
+
+    private static final int MSG_OPEN = 0;
+    private static final int MSG_CLOSE = 1;
+    private static final int MSG_OPENSUBMENU = 2;
+
+    protected static float CENTER = (float) Math.PI / 2;
+    protected static float RAD24 = (float)(24 * Math.PI / 180);
+    protected static final float SWEEP_SLICE = 0.14f;
+    protected static final float SWEEP_ARC = 0.23f;
+
+    // geometry
+    private int mRadius;
+    private int mRadiusInc;
+
+    // the detection if touch is inside a slice is offset
+    // inbounds by this amount to allow the selection to show before the
+    // finger covers it
+    private int mTouchOffset;
+
+    private List<PieItem> mOpen;
+
+    private Paint mSelectedPaint;
+    private Paint mSubPaint;
+    private Paint mMenuArcPaint;
+
+    // touch handling
+    private PieItem mCurrentItem;
+
+    private Paint mFocusPaint;
+    private int mSuccessColor;
+    private int mFailColor;
+    private int mCircleSize;
+    private int mFocusX;
+    private int mFocusY;
+    private int mCenterX;
+    private int mCenterY;
+    private int mArcCenterY;
+    private int mSliceCenterY;
+    private int mPieCenterX;
+    private int mPieCenterY;
+    private int mSliceRadius;
+    private int mArcRadius;
+    private int mArcOffset;
+
+    private int mDialAngle;
+    private RectF mCircle;
+    private RectF mDial;
+    private Point mPoint1;
+    private Point mPoint2;
+    private int mStartAnimationAngle;
+    private boolean mFocused;
+    private int mInnerOffset;
+    private int mOuterStroke;
+    private int mInnerStroke;
+    private boolean mTapMode;
+    private boolean mBlockFocus;
+    private int mTouchSlopSquared;
+    private Point mDown;
+    private boolean mOpening;
+    private LinearAnimation mXFade;
+    private LinearAnimation mFadeIn;
+    private FadeOutAnimation mFadeOut;
+    private LinearAnimation mSlice;
+    private volatile boolean mFocusCancelled;
+    private PointF mPolar = new PointF();
+    private TextDrawable mLabel;
+    private int mDeadZone;
+    private int mAngleZone;
+    private float mCenterAngle;
+
+
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+            case MSG_OPEN:
+                if (mListener != null) {
+                    mListener.onPieOpened(mPieCenterX, mPieCenterY);
+                }
+                break;
+            case MSG_CLOSE:
+                if (mListener != null) {
+                    mListener.onPieClosed();
+                }
+                break;
+            case MSG_OPENSUBMENU:
+                onEnterOpen();
+                break;
+            }
+
+        }
+    };
+
+    private PieListener mListener;
+
+    static public interface PieListener {
+        public void onPieOpened(int centerX, int centerY);
+        public void onPieClosed();
+    }
+
+    public void setPieListener(PieListener pl) {
+        mListener = pl;
+    }
+
+    public PieRenderer(Context context) {
+        init(context);
+    }
+
+    private void init(Context ctx) {
+        setVisible(false);
+        mOpen = new ArrayList<PieItem>();
+        mOpen.add(new PieItem(null, 0));
+        Resources res = ctx.getResources();
+        mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
+        mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
+        mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
+        mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
+        mSelectedPaint = new Paint();
+        mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
+        mSelectedPaint.setAntiAlias(true);
+        mSubPaint = new Paint();
+        mSubPaint.setAntiAlias(true);
+        mSubPaint.setColor(Color.argb(200, 250, 230, 128));
+        mFocusPaint = new Paint();
+        mFocusPaint.setAntiAlias(true);
+        mFocusPaint.setColor(Color.WHITE);
+        mFocusPaint.setStyle(Paint.Style.STROKE);
+        mSuccessColor = Color.GREEN;
+        mFailColor = Color.RED;
+        mCircle = new RectF();
+        mDial = new RectF();
+        mPoint1 = new Point();
+        mPoint2 = new Point();
+        mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
+        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
+        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
+        mState = STATE_IDLE;
+        mBlockFocus = false;
+        mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
+        mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
+        mDown = new Point();
+        mMenuArcPaint = new Paint();
+        mMenuArcPaint.setAntiAlias(true);
+        mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
+        mMenuArcPaint.setStrokeWidth(10);
+        mMenuArcPaint.setStyle(Paint.Style.STROKE);
+        mSliceRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
+        mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
+        mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
+        mLabel = new TextDrawable(res);
+        mLabel.setDropShadow(true);
+        mDeadZone = res.getDimensionPixelSize(R.dimen.pie_deadzone_width);
+        mAngleZone = res.getDimensionPixelSize(R.dimen.pie_anglezone_width);
+    }
+
+    private PieItem getRoot() {
+        return mOpen.get(0);
+    }
+
+    public boolean showsItems() {
+        return mTapMode;
+    }
+
+    public void addItem(PieItem item) {
+        // add the item to the pie itself
+        getRoot().addItem(item);
+    }
+
+    public void clearItems() {
+        getRoot().clearItems();
+    }
+
+    public void showInCenter() {
+        if ((mState == STATE_PIE) && isVisible()) {
+            mTapMode = false;
+            show(false);
+        } else {
+            if (mState != STATE_IDLE) {
+                cancelFocus();
+            }
+            mState = STATE_PIE;
+            resetPieCenter();
+            setCenter(mPieCenterX, mPieCenterY);
+            mTapMode = true;
+            show(true);
+        }
+    }
+
+    public void hide() {
+        show(false);
+    }
+
+    /**
+     * guaranteed has center set
+     * @param show
+     */
+    private void show(boolean show) {
+        if (show) {
+            if (mXFade != null) {
+                mXFade.cancel();
+            }
+            mState = STATE_PIE;
+            // ensure clean state
+            mCurrentItem = null;
+            PieItem root = getRoot();
+            for (PieItem openItem : mOpen) {
+                if (openItem.hasItems()) {
+                    for (PieItem item : openItem.getItems()) {
+                        item.setSelected(false);
+                    }
+                }
+            }
+            mLabel.setText("");
+            mOpen.clear();
+            mOpen.add(root);
+            layoutPie();
+            fadeIn();
+        } else {
+            mState = STATE_IDLE;
+            mTapMode = false;
+            if (mXFade != null) {
+                mXFade.cancel();
+            }
+            if (mLabel != null) {
+                mLabel.setText("");
+            }
+        }
+        setVisible(show);
+        mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
+    }
+
+    private void fadeIn() {
+        mFadeIn = new LinearAnimation(0, 1);
+        mFadeIn.setDuration(PIE_FADE_IN_DURATION);
+        mFadeIn.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                mFadeIn = null;
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        });
+        mFadeIn.startNow();
+        mOverlay.startAnimation(mFadeIn);
+    }
+
+    public void setCenter(int x, int y) {
+        mPieCenterX = x;
+        mPieCenterY = y;
+        mSliceCenterY = y + mSliceRadius - mArcOffset;
+        mArcCenterY = y - mArcOffset + mArcRadius;
+    }
+
+    @Override
+    public void layout(int l, int t, int r, int b) {
+        super.layout(l, t, r, b);
+        mCenterX = (r - l) / 2;
+        mCenterY = (b - t) / 2;
+
+        mFocusX = mCenterX;
+        mFocusY = mCenterY;
+        resetPieCenter();
+        setCircle(mFocusX, mFocusY);
+        if (isVisible() && mState == STATE_PIE) {
+            setCenter(mPieCenterX, mPieCenterY);
+            layoutPie();
+        }
+    }
+
+    private void resetPieCenter() {
+        mPieCenterX = mCenterX;
+        mPieCenterY = (int) (getHeight() - 2.5f * mDeadZone);
+    }
+
+    private void layoutPie() {
+        mCenterAngle = getCenterAngle();
+        layoutItems(0, getRoot().getItems());
+        layoutLabel(getLevel());
+    }
+
+    private void layoutLabel(int level) {
+        int x = mPieCenterX - (int) (FloatMath.sin(mCenterAngle - CENTER)
+                * (mArcRadius + (level + 2) * mRadiusInc));
+        int y = mArcCenterY - mArcRadius - (level + 2) * mRadiusInc;
+        int w = mLabel.getIntrinsicWidth();
+        int h = mLabel.getIntrinsicHeight();
+        mLabel.setBounds(x - w/2, y - h/2, x + w/2, y + h/2);
+    }
+
+    private void layoutItems(int level, List<PieItem> items) {
+        int extend = 1;
+        Path path = makeSlice(getDegrees(0) + extend, getDegrees(SWEEP_ARC) - extend,
+                mArcRadius, mArcRadius + mRadiusInc + mRadiusInc / 4,
+                mPieCenterX, mArcCenterY - level * mRadiusInc);
+        final int count = items.size();
+        int pos = 0;
+        for (PieItem item : items) {
+            // shared between items
+            item.setPath(path);
+            float angle = getArcCenter(item, pos, count);
+            int w = item.getIntrinsicWidth();
+            int h = item.getIntrinsicHeight();
+            // move views to outer border
+            int r = mArcRadius + mRadiusInc * 2 / 3;
+            int x = (int) (r * Math.cos(angle));
+            int y = mArcCenterY - (level * mRadiusInc) - (int) (r * Math.sin(angle)) - h / 2;
+            x = mPieCenterX + x - w / 2;
+            item.setBounds(x, y, x + w, y + h);
+            item.setLevel(level);
+            if (item.hasItems()) {
+                layoutItems(level + 1, item.getItems());
+            }
+            pos++;
+        }
+    }
+
+    private Path makeSlice(float start, float end, int inner, int outer, int cx, int cy) {
+        RectF bb =
+                new RectF(cx - outer, cy - outer, cx + outer,
+                        cy + outer);
+        RectF bbi =
+                new RectF(cx - inner, cy - inner, cx + inner,
+                        cy + inner);
+        Path path = new Path();
+        path.arcTo(bb, start, end - start, true);
+        path.arcTo(bbi, end, start - end);
+        path.close();
+        return path;
+    }
+
+    private float getArcCenter(PieItem item, int pos, int count) {
+        return getCenter(pos, count, SWEEP_ARC);
+    }
+
+    private float getSliceCenter(PieItem item, int pos, int count) {
+        float center = (getCenterAngle() - CENTER) * 0.5f + CENTER;
+        return center + (count - 1) * SWEEP_SLICE / 2f
+                - pos * SWEEP_SLICE;
+    }
+
+    private float getCenter(int pos, int count, float sweep) {
+        return mCenterAngle + (count - 1) * sweep / 2f - pos * sweep;
+    }
+
+    private float getCenterAngle() {
+        float center = CENTER;
+        if (mPieCenterX < mDeadZone + mAngleZone) {
+            center = CENTER - (mAngleZone - mPieCenterX + mDeadZone) * RAD24
+                    / (float) mAngleZone;
+        } else if (mPieCenterX > getWidth() - mDeadZone - mAngleZone) {
+            center = CENTER + (mPieCenterX - (getWidth() - mDeadZone - mAngleZone)) * RAD24
+                    / (float) mAngleZone;
+        }
+        return center;
+    }
+
+    /**
+     * converts a
+     * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
+     * @return skia angle
+     */
+    private float getDegrees(double angle) {
+        return (float) (360 - 180 * angle / Math.PI);
+    }
+
+    private void startFadeOut(final PieItem item) {
+        if (mFadeIn != null) {
+            mFadeIn.cancel();
+        }
+        if (mXFade != null) {
+            mXFade.cancel();
+        }
+        mFadeOut = new FadeOutAnimation();
+        mFadeOut.setDuration(PIE_FADE_OUT_DURATION);
+        mFadeOut.setAnimationListener(new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {
+            }
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                item.performClick();
+                mFadeOut = null;
+                deselect();
+                show(false);
+                mOverlay.setAlpha(1);
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {
+            }
+        });
+        mFadeOut.startNow();
+        mOverlay.startAnimation(mFadeOut);
+    }
+
+    // root does not count
+    private boolean hasOpenItem() {
+        return mOpen.size() > 1;
+    }
+
+    // pop an item of the open item stack
+    private PieItem closeOpenItem() {
+        PieItem item = getOpenItem();
+        mOpen.remove(mOpen.size() -1);
+        return item;
+    }
+
+    private PieItem getOpenItem() {
+        return mOpen.get(mOpen.size() - 1);
+    }
+
+    // return the children either the root or parent of the current open item
+    private PieItem getParent() {
+        return mOpen.get(Math.max(0, mOpen.size() - 2));
+    }
+
+    private int getLevel() {
+        return mOpen.size() - 1;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        float alpha = 1;
+        if (mXFade != null) {
+            alpha = mXFade.getValue();
+        } else if (mFadeIn != null) {
+            alpha = mFadeIn.getValue();
+        } else if (mFadeOut != null) {
+            alpha = mFadeOut.getValue();
+        }
+        int state = canvas.save();
+        if (mFadeIn != null) {
+            float sf = 0.9f + alpha * 0.1f;
+            canvas.scale(sf, sf, mPieCenterX, mPieCenterY);
+        }
+        if (mState != STATE_PIE) {
+            drawFocus(canvas);
+        }
+        if (mState == STATE_FINISHING) {
+            canvas.restoreToCount(state);
+            return;
+        }
+        if (mState != STATE_PIE) return;
+        if (!hasOpenItem() || (mXFade != null)) {
+            // draw base menu
+            drawArc(canvas, getLevel(), getParent());
+            List<PieItem> items = getParent().getItems();
+            final int count = items.size();
+            int pos = 0;
+            for (PieItem item : getParent().getItems()) {
+                drawItem(Math.max(0, mOpen.size() - 2), pos, count, canvas, item, alpha);
+                pos++;
+            }
+            mLabel.draw(canvas);
+        }
+        if (hasOpenItem()) {
+            int level = getLevel();
+            drawArc(canvas, level, getOpenItem());
+            List<PieItem> items = getOpenItem().getItems();
+            final int count = items.size();
+            int pos = 0;
+            for (PieItem inner : items) {
+                if (mFadeOut != null) {
+                    drawItem(level, pos, count, canvas, inner, alpha);
+                } else {
+                    drawItem(level, pos, count, canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
+                }
+                pos++;
+            }
+            mLabel.draw(canvas);
+        }
+        canvas.restoreToCount(state);
+    }
+
+    private void drawArc(Canvas canvas, int level, PieItem item) {
+        // arc
+        if (mState == STATE_PIE) {
+            final int count = item.getItems().size();
+            float start = mCenterAngle + (count * SWEEP_ARC / 2f);
+            float end =  mCenterAngle - (count * SWEEP_ARC / 2f);
+            int cy = mArcCenterY - level * mRadiusInc;
+            canvas.drawArc(new RectF(mPieCenterX - mArcRadius, cy - mArcRadius,
+                    mPieCenterX + mArcRadius, cy + mArcRadius),
+                    getDegrees(end), getDegrees(start) - getDegrees(end), false, mMenuArcPaint);
+        }
+    }
+
+    private void drawItem(int level, int pos, int count, Canvas canvas, PieItem item, float alpha) {
+        if (mState == STATE_PIE) {
+            if (item.getPath() != null) {
+                int y = mArcCenterY - level * mRadiusInc;
+                if (item.isSelected()) {
+                    Paint p = mSelectedPaint;
+                    int state = canvas.save();
+                    float angle = 0;
+                    if (mSlice != null) {
+                        angle = mSlice.getValue();
+                    } else {
+                        angle = getArcCenter(item, pos, count) - SWEEP_ARC / 2f;
+                    }
+                    angle = getDegrees(angle);
+                    canvas.rotate(angle, mPieCenterX, y);
+                    if (mFadeOut != null) {
+                        p.setAlpha((int)(255 * alpha));
+                    }
+                    canvas.drawPath(item.getPath(), p);
+                    if (mFadeOut != null) {
+                        p.setAlpha(255);
+                    }
+                    canvas.restoreToCount(state);
+                }
+                if (mFadeOut == null) {
+                    alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
+                    // draw the item view
+                    item.setAlpha(alpha);
+                }
+                item.draw(canvas);
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent evt) {
+        float x = evt.getX();
+        float y = evt.getY();
+        int action = evt.getActionMasked();
+        getPolar(x, y, !mTapMode, mPolar);
+        if (MotionEvent.ACTION_DOWN == action) {
+            if ((x < mDeadZone) || (x > getWidth() - mDeadZone)) {
+                return false;
+            }
+            mDown.x = (int) evt.getX();
+            mDown.y = (int) evt.getY();
+            mOpening = false;
+            if (mTapMode) {
+                PieItem item = findItem(mPolar);
+                if ((item != null) && (mCurrentItem != item)) {
+                    mState = STATE_PIE;
+                    onEnter(item);
+                }
+            } else {
+                setCenter((int) x, (int) y);
+                show(true);
+            }
+            return true;
+        } else if (MotionEvent.ACTION_UP == action) {
+            if (isVisible()) {
+                PieItem item = mCurrentItem;
+                if (mTapMode) {
+                    item = findItem(mPolar);
+                    if (mOpening) {
+                        mOpening = false;
+                        return true;
+                    }
+                }
+                if (item == null) {
+                    mTapMode = false;
+                    show(false);
+                } else if (!mOpening && !item.hasItems()) {
+                        startFadeOut(item);
+                        mTapMode = false;
+                } else {
+                    mTapMode = true;
+                }
+                return true;
+            }
+        } else if (MotionEvent.ACTION_CANCEL == action) {
+            if (isVisible() || mTapMode) {
+                show(false);
+            }
+            deselect();
+            mHandler.removeMessages(MSG_OPENSUBMENU);
+            return false;
+        } else if (MotionEvent.ACTION_MOVE == action) {
+            if (pulledToCenter(mPolar)) {
+                mHandler.removeMessages(MSG_OPENSUBMENU);
+                if (hasOpenItem()) {
+                    if (mCurrentItem != null) {
+                        mCurrentItem.setSelected(false);
+                    }
+                    closeOpenItem();
+                    mCurrentItem = null;
+                } else {
+                    deselect();
+                }
+                mLabel.setText("");
+                return false;
+            }
+            PieItem item = findItem(mPolar);
+            boolean moved = hasMoved(evt);
+            if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
+                mHandler.removeMessages(MSG_OPENSUBMENU);
+                // only select if we didn't just open or have moved past slop
+                if (moved) {
+                    // switch back to swipe mode
+                    mTapMode = false;
+                }
+                onEnterSelect(item);
+                mHandler.sendEmptyMessageDelayed(MSG_OPENSUBMENU, PIE_OPEN_SUB_DELAY);
+            }
+        }
+        return false;
+    }
+
+    private boolean pulledToCenter(PointF polarCoords) {
+        return polarCoords.y < mArcRadius - mRadiusInc;
+    }
+
+    private boolean inside(PointF polar, PieItem item, int pos, int count) {
+        float start = getSliceCenter(item, pos, count) - SWEEP_SLICE / 2f;
+        boolean res =  (mArcRadius < polar.y)
+                && (start < polar.x)
+                && (start + SWEEP_SLICE > polar.x)
+                && (!mTapMode || (mArcRadius + mRadiusInc > polar.y));
+        return res;
+    }
+
+    private void getPolar(float x, float y, boolean useOffset, PointF res) {
+        // get angle and radius from x/y
+        res.x = (float) Math.PI / 2;
+        x = x - mPieCenterX;
+        float y1 = mSliceCenterY - getLevel() * mRadiusInc - y;
+        float y2 = mArcCenterY - getLevel() * mRadiusInc - y;
+        res.y = (float) Math.sqrt(x * x + y2 * y2);
+        if (x != 0) {
+            res.x = (float) Math.atan2(y1,  x);
+            if (res.x < 0) {
+                res.x = (float) (2 * Math.PI + res.x);
+            }
+        }
+        res.y = res.y + (useOffset ? mTouchOffset : 0);
+    }
+
+    private boolean hasMoved(MotionEvent e) {
+        return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
+                + (e.getY() - mDown.y) * (e.getY() - mDown.y);
+    }
+
+    private void onEnterSelect(PieItem item) {
+        if (mCurrentItem != null) {
+            mCurrentItem.setSelected(false);
+        }
+        if (item != null && item.isEnabled()) {
+            moveSelection(mCurrentItem, item);
+            item.setSelected(true);
+            mCurrentItem = item;
+            mLabel.setText(mCurrentItem.getLabel());
+            layoutLabel(getLevel());
+        } else {
+            mCurrentItem = null;
+        }
+    }
+
+    private void onEnterOpen() {
+        if ((mCurrentItem != null) && (mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
+            openCurrentItem();
+        }
+    }
+
+    /**
+     * enter a slice for a view
+     * updates model only
+     * @param item
+     */
+    private void onEnter(PieItem item) {
+        if (mCurrentItem != null) {
+            mCurrentItem.setSelected(false);
+        }
+        if (item != null && item.isEnabled()) {
+            item.setSelected(true);
+            mCurrentItem = item;
+            mLabel.setText(mCurrentItem.getLabel());
+            if ((mCurrentItem != getOpenItem()) && mCurrentItem.hasItems()) {
+                openCurrentItem();
+                layoutLabel(getLevel());
+            }
+        } else {
+            mCurrentItem = null;
+        }
+    }
+
+    private void deselect() {
+        if (mCurrentItem != null) {
+            mCurrentItem.setSelected(false);
+        }
+        if (hasOpenItem()) {
+            PieItem item = closeOpenItem();
+            onEnter(item);
+        } else {
+            mCurrentItem = null;
+        }
+    }
+
+    private int getItemPos(PieItem target) {
+        List<PieItem> items = getOpenItem().getItems();
+        return items.indexOf(target);
+    }
+
+    private int getCurrentCount() {
+        return getOpenItem().getItems().size();
+    }
+
+    private void moveSelection(PieItem from, PieItem to) {
+        final int count = getCurrentCount();
+        final int fromPos = getItemPos(from);
+        final int toPos = getItemPos(to);
+        if (fromPos != -1 && toPos != -1) {
+            float startAngle = getArcCenter(from, getItemPos(from), count)
+                    - SWEEP_ARC / 2f;
+            float endAngle = getArcCenter(to, getItemPos(to), count)
+                    - SWEEP_ARC / 2f;
+            mSlice = new LinearAnimation(startAngle, endAngle);
+            mSlice.setDuration(PIE_SLICE_DURATION);
+            mSlice.setAnimationListener(new AnimationListener() {
+                @Override
+                public void onAnimationEnd(Animation arg0) {
+                    mSlice = null;
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation arg0) {
+                }
+
+                @Override
+                public void onAnimationStart(Animation arg0) {
+                }
+            });
+            mOverlay.startAnimation(mSlice);
+        }
+    }
+
+    private void openCurrentItem() {
+        if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
+            mOpen.add(mCurrentItem);
+            layoutLabel(getLevel());
+            mOpening = true;
+            if (mFadeIn != null) {
+                mFadeIn.cancel();
+            }
+            mXFade = new LinearAnimation(1, 0);
+            mXFade.setDuration(PIE_XFADE_DURATION);
+            final PieItem ci = mCurrentItem;
+            mXFade.setAnimationListener(new AnimationListener() {
+                @Override
+                public void onAnimationStart(Animation animation) {
+                }
+
+                @Override
+                public void onAnimationEnd(Animation animation) {
+                    mXFade = null;
+                    ci.setSelected(false);
+                    mOpening = false;
+                }
+
+                @Override
+                public void onAnimationRepeat(Animation animation) {
+                }
+            });
+            mXFade.startNow();
+            mOverlay.startAnimation(mXFade);
+        }
+    }
+
+    /**
+     * @param polar x: angle, y: dist
+     * @return the item at angle/dist or null
+     */
+    private PieItem findItem(PointF polar) {
+        // find the matching item:
+        List<PieItem> items = getOpenItem().getItems();
+        final int count = items.size();
+        int pos = 0;
+        for (PieItem item : items) {
+            if (inside(polar, item, pos, count)) {
+                return item;
+            }
+            pos++;
+        }
+        return null;
+    }
+
+
+    @Override
+    public boolean handlesTouch() {
+        return true;
+    }
+
+    // focus specific code
+
+    public void setBlockFocus(boolean blocked) {
+        mBlockFocus = blocked;
+        if (blocked) {
+            clear();
+        }
+    }
+
+    public void setFocus(int x, int y) {
+        mFocusX = x;
+        mFocusY = y;
+        setCircle(mFocusX, mFocusY);
+    }
+
+    public void alignFocus(int x, int y) {
+        mOverlay.removeCallbacks(mDisappear);
+        mAnimation.cancel();
+        mAnimation.reset();
+        mFocusX = x;
+        mFocusY = y;
+        mDialAngle = DIAL_HORIZONTAL;
+        setCircle(x, y);
+        mFocused = false;
+    }
+
+    public int getSize() {
+        return 2 * mCircleSize;
+    }
+
+    private int getRandomRange() {
+        return (int)(-60 + 120 * Math.random());
+    }
+
+    private void setCircle(int cx, int cy) {
+        mCircle.set(cx - mCircleSize, cy - mCircleSize,
+                cx + mCircleSize, cy + mCircleSize);
+        mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
+                cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
+    }
+
+    public void drawFocus(Canvas canvas) {
+        if (mBlockFocus) return;
+        mFocusPaint.setStrokeWidth(mOuterStroke);
+        canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
+        if (mState == STATE_PIE) return;
+        int color = mFocusPaint.getColor();
+        if (mState == STATE_FINISHING) {
+            mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
+        }
+        mFocusPaint.setStrokeWidth(mInnerStroke);
+        drawLine(canvas, mDialAngle, mFocusPaint);
+        drawLine(canvas, mDialAngle + 45, mFocusPaint);
+        drawLine(canvas, mDialAngle + 180, mFocusPaint);
+        drawLine(canvas, mDialAngle + 225, mFocusPaint);
+        canvas.save();
+        // rotate the arc instead of its offset to better use framework's shape caching
+        canvas.rotate(mDialAngle, mFocusX, mFocusY);
+        canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
+        canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
+        canvas.restore();
+        mFocusPaint.setColor(color);
+    }
+
+    private void drawLine(Canvas canvas, int angle, Paint p) {
+        convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
+        convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
+        canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
+                mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
+    }
+
+    private static void convertCart(int angle, int radius, Point out) {
+        double a = 2 * Math.PI * (angle % 360) / 360;
+        out.x = (int) (radius * Math.cos(a) + 0.5);
+        out.y = (int) (radius * Math.sin(a) + 0.5);
+    }
+
+    @Override
+    public void showStart() {
+        if (mState == STATE_PIE) return;
+        cancelFocus();
+        mStartAnimationAngle = 67;
+        int range = getRandomRange();
+        startAnimation(SCALING_UP_TIME,
+                false, mStartAnimationAngle, mStartAnimationAngle + range);
+        mState = STATE_FOCUSING;
+    }
+
+    @Override
+    public void showSuccess(boolean timeout) {
+        if (mState == STATE_FOCUSING) {
+            startAnimation(SCALING_DOWN_TIME,
+                    timeout, mStartAnimationAngle);
+            mState = STATE_FINISHING;
+            mFocused = true;
+        }
+    }
+
+    @Override
+    public void showFail(boolean timeout) {
+        if (mState == STATE_FOCUSING) {
+            startAnimation(SCALING_DOWN_TIME,
+                    timeout, mStartAnimationAngle);
+            mState = STATE_FINISHING;
+            mFocused = false;
+        }
+    }
+
+    private void cancelFocus() {
+        mFocusCancelled = true;
+        mOverlay.removeCallbacks(mDisappear);
+        if (mAnimation != null && !mAnimation.hasEnded()) {
+            mAnimation.cancel();
+        }
+        mFocusCancelled = false;
+        mFocused = false;
+        mState = STATE_IDLE;
+    }
+
+    @Override
+    public void clear() {
+        if (mState == STATE_PIE) return;
+        cancelFocus();
+        mOverlay.post(mDisappear);
+    }
+
+    private void startAnimation(long duration, boolean timeout,
+            float toScale) {
+        startAnimation(duration, timeout, mDialAngle,
+                toScale);
+    }
+
+    private void startAnimation(long duration, boolean timeout,
+            float fromScale, float toScale) {
+        setVisible(true);
+        mAnimation.reset();
+        mAnimation.setDuration(duration);
+        mAnimation.setScale(fromScale, toScale);
+        mAnimation.setAnimationListener(timeout ? mEndAction : null);
+        mOverlay.startAnimation(mAnimation);
+        update();
+    }
+
+    private class EndAction implements Animation.AnimationListener {
+        @Override
+        public void onAnimationEnd(Animation animation) {
+            // Keep the focus indicator for some time.
+            if (!mFocusCancelled) {
+                mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
+            }
+        }
+
+        @Override
+        public void onAnimationRepeat(Animation animation) {
+        }
+
+        @Override
+        public void onAnimationStart(Animation animation) {
+        }
+    }
+
+    private class Disappear implements Runnable {
+        @Override
+        public void run() {
+            if (mState == STATE_PIE) return;
+            setVisible(false);
+            mFocusX = mCenterX;
+            mFocusY = mCenterY;
+            mState = STATE_IDLE;
+            setCircle(mFocusX, mFocusY);
+            mFocused = false;
+        }
+    }
+
+    private class FadeOutAnimation extends Animation {
+
+        private float mAlpha;
+
+        public float getValue() {
+            return mAlpha;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            if (interpolatedTime < 0.2) {
+                mAlpha = 1;
+            } else if (interpolatedTime < 0.3) {
+                mAlpha = 0;
+            } else {
+                mAlpha = 1 - (interpolatedTime - 0.3f) / 0.7f;
+            }
+        }
+    }
+
+    private class ScaleAnimation extends Animation {
+        private float mFrom = 1f;
+        private float mTo = 1f;
+
+        public ScaleAnimation() {
+            setFillAfter(true);
+        }
+
+        public void setScale(float from, float to) {
+            mFrom = from;
+            mTo = to;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mDialAngle = (int)(mFrom + (mTo - mFrom) * interpolatedTime);
+        }
+    }
+
+    private class LinearAnimation extends Animation {
+        private float mFrom;
+        private float mTo;
+        private float mValue;
+
+        public LinearAnimation(float from, float to) {
+            setFillAfter(true);
+            setInterpolator(new LinearInterpolator());
+            mFrom = from;
+            mTo = to;
+        }
+
+        public float getValue() {
+            return mValue;
+        }
+
+        @Override
+        protected void applyTransformation(float interpolatedTime, Transformation t) {
+            mValue = (mFrom + (mTo - mFrom) * interpolatedTime);
+        }
+    }
+
+}
diff --git a/src/com/android/camera/ui/PopupManager.java b/src/com/android/camera/ui/PopupManager.java
new file mode 100644
index 0000000..0dcf34f
--- /dev/null
+++ b/src/com/android/camera/ui/PopupManager.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A manager which notifies the event of a new popup in order to dismiss the
+ * old popup if exists.
+ */
+public class PopupManager {
+    private static HashMap<Context, PopupManager> sMap =
+            new HashMap<Context, PopupManager>();
+
+    public interface OnOtherPopupShowedListener {
+        public void onOtherPopupShowed();
+    }
+
+    private PopupManager() {}
+
+    private ArrayList<OnOtherPopupShowedListener> mListeners = new ArrayList<OnOtherPopupShowedListener>();
+
+    public void notifyShowPopup(View view) {
+        for (OnOtherPopupShowedListener listener : mListeners) {
+            if ((View) listener != view) {
+                listener.onOtherPopupShowed();
+            }
+        }
+    }
+
+    public void setOnOtherPopupShowedListener(OnOtherPopupShowedListener listener) {
+        mListeners.add(listener);
+    }
+
+    public static PopupManager getInstance(Context context) {
+        PopupManager instance = sMap.get(context);
+        if (instance == null) {
+            instance = new PopupManager();
+            sMap.put(context, instance);
+        }
+        return instance;
+    }
+
+    public static void removeInstance(Context context) {
+        PopupManager instance = sMap.get(context);
+        sMap.remove(context);
+    }
+}
diff --git a/src/com/android/camera/ui/PreviewSurfaceView.java b/src/com/android/camera/ui/PreviewSurfaceView.java
new file mode 100644
index 0000000..9a428e2
--- /dev/null
+++ b/src/com/android/camera/ui/PreviewSurfaceView.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.common.ApiHelper;
+
+public class PreviewSurfaceView extends SurfaceView {
+    public PreviewSurfaceView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setZOrderMediaOverlay(true);
+        getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+    }
+
+    public void shrink() {
+        setLayoutSize(1);
+    }
+
+    public void expand() {
+        setLayoutSize(ViewGroup.LayoutParams.MATCH_PARENT);
+    }
+
+    private void setLayoutSize(int size) {
+        ViewGroup.LayoutParams p = getLayoutParams();
+        if (p.width != size || p.height != size) {
+            p.width = size;
+            p.height = size;
+            setLayoutParams(p);
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/RenderOverlay.java b/src/com/android/camera/ui/RenderOverlay.java
new file mode 100644
index 0000000..ba25915
--- /dev/null
+++ b/src/com/android/camera/ui/RenderOverlay.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RenderOverlay extends FrameLayout {
+
+    private static final String TAG = "CAM_Overlay";
+
+    interface Renderer {
+
+        public boolean handlesTouch();
+        public boolean onTouchEvent(MotionEvent evt);
+        public void setOverlay(RenderOverlay overlay);
+        public void layout(int left, int top, int right, int bottom);
+        public void draw(Canvas canvas);
+
+    }
+
+    private RenderView mRenderView;
+    private List<Renderer> mClients;
+
+    // reverse list of touch clients
+    private List<Renderer> mTouchClients;
+    private int[] mPosition = new int[2];
+
+    public RenderOverlay(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mRenderView = new RenderView(context);
+        addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT,
+                LayoutParams.MATCH_PARENT));
+        mClients = new ArrayList<Renderer>(10);
+        mTouchClients = new ArrayList<Renderer>(10);
+        setWillNotDraw(false);
+    }
+
+    public void addRenderer(Renderer renderer) {
+        mClients.add(renderer);
+        renderer.setOverlay(this);
+        if (renderer.handlesTouch()) {
+            mTouchClients.add(0, renderer);
+        }
+        renderer.layout(getLeft(), getTop(), getRight(), getBottom());
+    }
+
+    public void addRenderer(int pos, Renderer renderer) {
+        mClients.add(pos, renderer);
+        renderer.setOverlay(this);
+        renderer.layout(getLeft(), getTop(), getRight(), getBottom());
+    }
+
+    public void remove(Renderer renderer) {
+        mClients.remove(renderer);
+        renderer.setOverlay(null);
+    }
+
+    public int getClientSize() {
+        return mClients.size();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        return false;
+    }
+
+    public boolean directDispatchTouch(MotionEvent m, Renderer target) {
+        mRenderView.setTouchTarget(target);
+        boolean res = super.dispatchTouchEvent(m);
+        mRenderView.setTouchTarget(null);
+        return res;
+    }
+
+    private void adjustPosition() {
+        getLocationInWindow(mPosition);
+    }
+
+    public int getWindowPositionX() {
+        return mPosition[0];
+    }
+
+    public int getWindowPositionY() {
+        return mPosition[1];
+    }
+
+    public void update() {
+        mRenderView.invalidate();
+    }
+
+    private class RenderView extends View {
+
+        private Renderer mTouchTarget;
+
+        public RenderView(Context context) {
+            super(context);
+            setWillNotDraw(false);
+        }
+
+        public void setTouchTarget(Renderer target) {
+            mTouchTarget = target;
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent evt) {
+            if (mTouchTarget != null) {
+                return mTouchTarget.onTouchEvent(evt);
+            }
+            if (mTouchClients != null) {
+                boolean res = false;
+                for (Renderer client : mTouchClients) {
+                    res |= client.onTouchEvent(evt);
+                }
+                return res;
+            }
+            return false;
+        }
+
+        @Override
+        public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            adjustPosition();
+            super.onLayout(changed, left,  top, right, bottom);
+            if (mClients == null) return;
+            for (Renderer renderer : mClients) {
+                renderer.layout(left, top, right, bottom);
+            }
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            super.draw(canvas);
+            if (mClients == null) return;
+            boolean redraw = false;
+            for (Renderer renderer : mClients) {
+                renderer.draw(canvas);
+                redraw = redraw || ((OverlayRenderer) renderer).isVisible();
+            }
+            if (redraw) {
+                invalidate();
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/camera/ui/Rotatable.java b/src/com/android/camera/ui/Rotatable.java
new file mode 100644
index 0000000..6d428b8
--- /dev/null
+++ b/src/com/android/camera/ui/Rotatable.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+public interface Rotatable {
+    // Set parameter 'animation' to true to have animation when rotation.
+    public void setOrientation(int orientation, boolean animation);
+}
diff --git a/src/com/android/camera/ui/RotatableLayout.java b/src/com/android/camera/ui/RotatableLayout.java
new file mode 100644
index 0000000..8355c88
--- /dev/null
+++ b/src/com/android/camera/ui/RotatableLayout.java
@@ -0,0 +1,239 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.android.camera.Util;
+
+/* RotatableLayout rotates itself as well as all its children when orientation
+ * changes. Specifically, when going from portrait to landscape, camera
+ * controls move from the bottom of the screen to right side of the screen
+ * (i.e. counter clockwise). Similarly, when the screen changes to portrait, we
+ * need to move the controls from right side to the bottom of the screen, which
+ * is a clockwise rotation.
+ */
+
+public class RotatableLayout extends FrameLayout {
+
+    private static final String TAG = "RotatableLayout";
+    // Initial orientation of the layout (ORIENTATION_PORTRAIT, or ORIENTATION_LANDSCAPE)
+    private int mInitialOrientation;
+    private int mPrevRotation;
+    private RotationListener mListener = null;
+    public interface RotationListener {
+        public void onRotation(int rotation);
+    }
+    public RotatableLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mInitialOrientation = getResources().getConfiguration().orientation;
+    }
+
+    public RotatableLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mInitialOrientation = getResources().getConfiguration().orientation;
+    }
+
+    public RotatableLayout(Context context) {
+        super(context);
+        mInitialOrientation = getResources().getConfiguration().orientation;
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        mPrevRotation = Util.getDisplayRotation((Activity) getContext());
+        // check if there is any rotation before the view is attached to window
+        int currentOrientation = getResources().getConfiguration().orientation;
+        if (mInitialOrientation == currentOrientation) {
+            return;
+        }
+        if (mInitialOrientation == Configuration.ORIENTATION_LANDSCAPE
+                && currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
+            rotateLayout(true);
+        } else if (mInitialOrientation == Configuration.ORIENTATION_PORTRAIT
+                && currentOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            rotateLayout(false);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+        int rotation = Util.getDisplayRotation((Activity) getContext());
+        if ((rotation - mPrevRotation + 360) % 180 == 0) {
+            mPrevRotation = rotation;
+            return;
+        }
+        boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
+        rotateLayout(clockwise);
+        mPrevRotation = rotation;
+    }
+
+    protected void rotateLayout(boolean clockwise) {
+        // Change the size of the layout
+        ViewGroup.LayoutParams lp = getLayoutParams();
+        int width = lp.width;
+        int height = lp.height;
+        lp.height = width;
+        lp.width = height;
+        setLayoutParams(lp);
+
+        // rotate all the children
+        rotateChildren(clockwise);
+    }
+
+    protected void rotateChildren(boolean clockwise) {
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            rotate(child, clockwise);
+        }
+        if (mListener != null) mListener.onRotation(clockwise ? 90 : 270);
+    }
+
+    protected void flipChildren() {
+        mPrevRotation = Util.getDisplayRotation((Activity) getContext());
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            flip(child);
+        }
+        if (mListener != null) mListener.onRotation(180);
+    }
+
+    public void setRotationListener(RotationListener listener) {
+        mListener = listener;
+    }
+
+    public static boolean isClockWiseRotation(int prevRotation, int currentRotation) {
+        if (prevRotation == (currentRotation + 90) % 360) {
+            return true;
+        }
+        return false;
+    }
+
+    public static void rotate(View view, boolean isClockwise) {
+        if (isClockwise) {
+            rotateClockwise(view);
+        } else {
+            rotateCounterClockwise(view);
+        }
+    }
+
+    private static boolean contains(int value, int mask) {
+        return (value & mask) == mask;
+    }
+
+    public static void rotateClockwise(View view) {
+        if (view == null) return;
+        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        int gravity = lp.gravity;
+        int ngravity = 0;
+        // rotate gravity
+        if (contains(gravity, Gravity.LEFT)) {
+            ngravity |= Gravity.TOP;
+        }
+        if (contains(gravity, Gravity.RIGHT)) {
+            ngravity |= Gravity.BOTTOM;
+        }
+        if (contains(gravity, Gravity.TOP)) {
+            ngravity |= Gravity.RIGHT;
+        }
+        if (contains(gravity, Gravity.BOTTOM)) {
+            ngravity |= Gravity.LEFT;
+        }
+        if (contains(gravity, Gravity.CENTER)) {
+            ngravity |= Gravity.CENTER;
+        }
+        if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
+            ngravity |= Gravity.CENTER_VERTICAL;
+        }
+        if (contains(gravity, Gravity.CENTER_VERTICAL)) {
+            ngravity |= Gravity.CENTER_HORIZONTAL;
+        }
+        lp.gravity = ngravity;
+        int ml = lp.leftMargin;
+        int mr = lp.rightMargin;
+        int mt = lp.topMargin;
+        int mb = lp.bottomMargin;
+        lp.leftMargin = mb;
+        lp.rightMargin = mt;
+        lp.topMargin = ml;
+        lp.bottomMargin = mr;
+        int width = lp.width;
+        int height = lp.height;
+        lp.width = height;
+        lp.height = width;
+        view.setLayoutParams(lp);
+    }
+
+    public static void rotateCounterClockwise(View view) {
+        if (view == null) return;
+        LayoutParams lp = (LayoutParams) view.getLayoutParams();
+        int gravity = lp.gravity;
+        int ngravity = 0;
+        // change gravity
+        if (contains(gravity, Gravity.RIGHT)) {
+            ngravity |= Gravity.TOP;
+        }
+        if (contains(gravity, Gravity.LEFT)) {
+            ngravity |= Gravity.BOTTOM;
+        }
+        if (contains(gravity, Gravity.TOP)) {
+            ngravity |= Gravity.LEFT;
+        }
+        if (contains(gravity, Gravity.BOTTOM)) {
+            ngravity |= Gravity.RIGHT;
+        }
+        if (contains(gravity, Gravity.CENTER)) {
+            ngravity |= Gravity.CENTER;
+        }
+        if (contains(gravity, Gravity.CENTER_HORIZONTAL)) {
+            ngravity |= Gravity.CENTER_VERTICAL;
+        }
+        if (contains(gravity, Gravity.CENTER_VERTICAL)) {
+            ngravity |= Gravity.CENTER_HORIZONTAL;
+        }
+        lp.gravity = ngravity;
+        int ml = lp.leftMargin;
+        int mr = lp.rightMargin;
+        int mt = lp.topMargin;
+        int mb = lp.bottomMargin;
+        lp.leftMargin = mt;
+        lp.rightMargin = mb;
+        lp.topMargin = mr;
+        lp.bottomMargin = ml;
+        int width = lp.width;
+        int height = lp.height;
+        lp.width = height;
+        lp.height = width;
+        view.setLayoutParams(lp);
+    }
+
+    // Rotate a given view 180 degrees
+    public static void flip(View view) {
+        rotateClockwise(view);
+        rotateClockwise(view);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/ui/RotateImageView.java b/src/com/android/camera/ui/RotateImageView.java
new file mode 100644
index 0000000..05e1a7c
--- /dev/null
+++ b/src/com/android/camera/ui/RotateImageView.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.TransitionDrawable;
+import android.media.ThumbnailUtils;
+import android.util.AttributeSet;
+import android.view.ViewGroup.LayoutParams;
+import android.view.animation.AnimationUtils;
+import android.widget.ImageView;
+
+/**
+ * A @{code ImageView} which can rotate it's content.
+ */
+public class RotateImageView extends TwoStateImageView implements Rotatable {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "RotateImageView";
+
+    private static final int ANIMATION_SPEED = 270; // 270 deg/sec
+
+    private int mCurrentDegree = 0; // [0, 359]
+    private int mStartDegree = 0;
+    private int mTargetDegree = 0;
+
+    private boolean mClockwise = false, mEnableAnimation = true;
+
+    private long mAnimationStartTime = 0;
+    private long mAnimationEndTime = 0;
+
+    public RotateImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public RotateImageView(Context context) {
+        super(context);
+    }
+
+    protected int getDegree() {
+        return mTargetDegree;
+    }
+
+    // Rotate the view counter-clockwise
+    @Override
+    public void setOrientation(int degree, boolean animation) {
+        mEnableAnimation = animation;
+        // make sure in the range of [0, 359]
+        degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
+        if (degree == mTargetDegree) return;
+
+        mTargetDegree = degree;
+        if (mEnableAnimation) {
+            mStartDegree = mCurrentDegree;
+            mAnimationStartTime = AnimationUtils.currentAnimationTimeMillis();
+
+            int diff = mTargetDegree - mCurrentDegree;
+            diff = diff >= 0 ? diff : 360 + diff; // make it in range [0, 359]
+
+            // Make it in range [-179, 180]. That's the shorted distance between the
+            // two angles
+            diff = diff > 180 ? diff - 360 : diff;
+
+            mClockwise = diff >= 0;
+            mAnimationEndTime = mAnimationStartTime
+                    + Math.abs(diff) * 1000 / ANIMATION_SPEED;
+        } else {
+            mCurrentDegree = mTargetDegree;
+        }
+
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        Drawable drawable = getDrawable();
+        if (drawable == null) return;
+
+        Rect bounds = drawable.getBounds();
+        int w = bounds.right - bounds.left;
+        int h = bounds.bottom - bounds.top;
+
+        if (w == 0 || h == 0) return; // nothing to draw
+
+        if (mCurrentDegree != mTargetDegree) {
+            long time = AnimationUtils.currentAnimationTimeMillis();
+            if (time < mAnimationEndTime) {
+                int deltaTime = (int)(time - mAnimationStartTime);
+                int degree = mStartDegree + ANIMATION_SPEED
+                        * (mClockwise ? deltaTime : -deltaTime) / 1000;
+                degree = degree >= 0 ? degree % 360 : degree % 360 + 360;
+                mCurrentDegree = degree;
+                invalidate();
+            } else {
+                mCurrentDegree = mTargetDegree;
+            }
+        }
+
+        int left = getPaddingLeft();
+        int top = getPaddingTop();
+        int right = getPaddingRight();
+        int bottom = getPaddingBottom();
+        int width = getWidth() - left - right;
+        int height = getHeight() - top - bottom;
+
+        int saveCount = canvas.getSaveCount();
+
+        // Scale down the image first if required.
+        if ((getScaleType() == ImageView.ScaleType.FIT_CENTER) &&
+                ((width < w) || (height < h))) {
+            float ratio = Math.min((float) width / w, (float) height / h);
+            canvas.scale(ratio, ratio, width / 2.0f, height / 2.0f);
+        }
+        canvas.translate(left + width / 2, top + height / 2);
+        canvas.rotate(-mCurrentDegree);
+        canvas.translate(-w / 2, -h / 2);
+        drawable.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    private Bitmap mThumb;
+    private Drawable[] mThumbs;
+    private TransitionDrawable mThumbTransition;
+
+    public void setBitmap(Bitmap bitmap) {
+        // Make sure uri and original are consistently both null or both
+        // non-null.
+        if (bitmap == null) {
+            mThumb = null;
+            mThumbs = null;
+            setImageDrawable(null);
+            setVisibility(GONE);
+            return;
+        }
+
+        LayoutParams param = getLayoutParams();
+        final int miniThumbWidth = param.width
+                - getPaddingLeft() - getPaddingRight();
+        final int miniThumbHeight = param.height
+                - getPaddingTop() - getPaddingBottom();
+        mThumb = ThumbnailUtils.extractThumbnail(
+                bitmap, miniThumbWidth, miniThumbHeight);
+        Drawable drawable;
+        if (mThumbs == null || !mEnableAnimation) {
+            mThumbs = new Drawable[2];
+            mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);
+            setImageDrawable(mThumbs[1]);
+        } else {
+            mThumbs[0] = mThumbs[1];
+            mThumbs[1] = new BitmapDrawable(getContext().getResources(), mThumb);
+            mThumbTransition = new TransitionDrawable(mThumbs);
+            setImageDrawable(mThumbTransition);
+            mThumbTransition.startTransition(500);
+        }
+        setVisibility(VISIBLE);
+    }
+}
diff --git a/src/com/android/camera/ui/RotateLayout.java b/src/com/android/camera/ui/RotateLayout.java
new file mode 100644
index 0000000..86f5c81
--- /dev/null
+++ b/src/com/android/camera/ui/RotateLayout.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010 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.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.MotionEventHelper;
+
+// A RotateLayout is designed to display a single item and provides the
+// capabilities to rotate the item.
+public class RotateLayout extends ViewGroup implements Rotatable {
+    @SuppressWarnings("unused")
+    private static final String TAG = "RotateLayout";
+    private int mOrientation;
+    private Matrix mMatrix = new Matrix();
+    protected View mChild;
+
+    public RotateLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // The transparent background here is a workaround of the render issue
+        // happened when the view is rotated as the device's orientation
+        // changed. The view looks fine in landscape. After rotation, the view
+        // is invisible.
+        setBackgroundResource(android.R.color.transparent);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    @Override
+    protected void onFinishInflate() {
+        mChild = getChildAt(0);
+        if (ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) {
+            mChild.setPivotX(0);
+            mChild.setPivotY(0);
+        }
+    }
+
+    @Override
+    protected void onLayout(
+            boolean change, int left, int top, int right, int bottom) {
+        int width = right - left;
+        int height = bottom - top;
+        switch (mOrientation) {
+            case 0:
+            case 180:
+                mChild.layout(0, 0, width, height);
+                break;
+            case 90:
+            case 270:
+                mChild.layout(0, 0, height, width);
+                break;
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (!ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) {
+            final int w = getMeasuredWidth();
+            final int h = getMeasuredHeight();
+            switch (mOrientation) {
+                case 0:
+                    mMatrix.setTranslate(0, 0);
+                    break;
+                case 90:
+                    mMatrix.setTranslate(0, -h);
+                    break;
+                case 180:
+                    mMatrix.setTranslate(-w, -h);
+                    break;
+                case 270:
+                    mMatrix.setTranslate(-w, 0);
+                    break;
+            }
+            mMatrix.postRotate(mOrientation);
+            event = MotionEventHelper.transformEvent(event, mMatrix);
+        }
+        return super.dispatchTouchEvent(event);
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        if (ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) {
+            super.dispatchDraw(canvas);
+        } else {
+            canvas.save();
+            int w = getMeasuredWidth();
+            int h = getMeasuredHeight();
+            switch (mOrientation) {
+                case 0:
+                    canvas.translate(0, 0);
+                    break;
+                case 90:
+                    canvas.translate(0, h);
+                    break;
+                case 180:
+                    canvas.translate(w, h);
+                    break;
+                case 270:
+                    canvas.translate(w, 0);
+                    break;
+            }
+            canvas.rotate(-mOrientation, 0, 0);
+            super.dispatchDraw(canvas);
+            canvas.restore();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    @Override
+    protected void onMeasure(int widthSpec, int heightSpec) {
+        int w = 0, h = 0;
+        switch(mOrientation) {
+            case 0:
+            case 180:
+                measureChild(mChild, widthSpec, heightSpec);
+                w = mChild.getMeasuredWidth();
+                h = mChild.getMeasuredHeight();
+                break;
+            case 90:
+            case 270:
+                measureChild(mChild, heightSpec, widthSpec);
+                w = mChild.getMeasuredHeight();
+                h = mChild.getMeasuredWidth();
+                break;
+        }
+        setMeasuredDimension(w, h);
+
+        if (ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES) {
+            switch (mOrientation) {
+                case 0:
+                    mChild.setTranslationX(0);
+                    mChild.setTranslationY(0);
+                    break;
+                case 90:
+                    mChild.setTranslationX(0);
+                    mChild.setTranslationY(h);
+                    break;
+                case 180:
+                    mChild.setTranslationX(w);
+                    mChild.setTranslationY(h);
+                    break;
+                case 270:
+                    mChild.setTranslationX(w);
+                    mChild.setTranslationY(0);
+                    break;
+            }
+            mChild.setRotation(-mOrientation);
+        }
+    }
+
+    @Override
+    public boolean shouldDelayChildPressedState() {
+        return false;
+    }
+
+    // Rotate the view counter-clockwise
+    @Override
+    public void setOrientation(int orientation, boolean animation) {
+        orientation = orientation % 360;
+        if (mOrientation == orientation) return;
+        mOrientation = orientation;
+        requestLayout();
+    }
+
+    public int getOrientation() {
+        return mOrientation;
+    }
+
+    @Override
+    public ViewParent invalidateChildInParent(int[] location, Rect r) {
+        if (!ApiHelper.HAS_VIEW_TRANSFORM_PROPERTIES && mOrientation != 0) {
+            // The workaround invalidates the entire rotate layout. After
+            // rotation, the correct area to invalidate may be larger than the
+            // size of the child. Ex: ListView. There is no way to invalidate
+            // only the necessary area.
+            r.set(0, 0, getWidth(), getHeight());
+        }
+        return super.invalidateChildInParent(location, r);
+    }
+}
diff --git a/src/com/android/camera/ui/RotateTextToast.java b/src/com/android/camera/ui/RotateTextToast.java
new file mode 100644
index 0000000..c78a258
--- /dev/null
+++ b/src/com/android/camera/ui/RotateTextToast.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class RotateTextToast {
+    private static final int TOAST_DURATION = 5000; // milliseconds
+    ViewGroup mLayoutRoot;
+    RotateLayout mToast;
+    Handler mHandler;
+
+    public RotateTextToast(Activity activity, int textResourceId, int orientation) {
+        mLayoutRoot = (ViewGroup) activity.getWindow().getDecorView();
+        LayoutInflater inflater = activity.getLayoutInflater();
+        View v = inflater.inflate(R.layout.rotate_text_toast, mLayoutRoot);
+        mToast = (RotateLayout) v.findViewById(R.id.rotate_toast);
+        TextView tv = (TextView) mToast.findViewById(R.id.message);
+        tv.setText(textResourceId);
+        mToast.setOrientation(orientation, false);
+        mHandler = new Handler();
+    }
+
+    private final Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            Util.fadeOut(mToast);
+            mLayoutRoot.removeView(mToast);
+            mToast = null;
+        }
+    };
+
+    public void show() {
+        mToast.setVisibility(View.VISIBLE);
+        mHandler.postDelayed(mRunnable, TOAST_DURATION);
+    }
+}
diff --git a/src/com/android/camera/ui/Switch.java b/src/com/android/camera/ui/Switch.java
new file mode 100644
index 0000000..ac21758
--- /dev/null
+++ b/src/com/android/camera/ui/Switch.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.CompoundButton;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.Arrays;
+
+/**
+ * A Switch is a two-state toggle switch widget that can select between two
+ * options. The user may drag the "thumb" back and forth to choose the selected option,
+ * or simply tap to toggle as if it were a checkbox.
+ */
+public class Switch extends CompoundButton {
+    private static final int TOUCH_MODE_IDLE = 0;
+    private static final int TOUCH_MODE_DOWN = 1;
+    private static final int TOUCH_MODE_DRAGGING = 2;
+
+    private Drawable mThumbDrawable;
+    private Drawable mTrackDrawable;
+    private int mThumbTextPadding;
+    private int mSwitchMinWidth;
+    private int mSwitchTextMaxWidth;
+    private int mSwitchPadding;
+    private CharSequence mTextOn;
+    private CharSequence mTextOff;
+
+    private int mTouchMode;
+    private int mTouchSlop;
+    private float mTouchX;
+    private float mTouchY;
+    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+    private int mMinFlingVelocity;
+
+    private float mThumbPosition;
+    private int mSwitchWidth;
+    private int mSwitchHeight;
+    private int mThumbWidth; // Does not include padding
+
+    private int mSwitchLeft;
+    private int mSwitchTop;
+    private int mSwitchRight;
+    private int mSwitchBottom;
+
+    private TextPaint mTextPaint;
+    private ColorStateList mTextColors;
+    private Layout mOnLayout;
+    private Layout mOffLayout;
+
+    @SuppressWarnings("hiding")
+    private final Rect mTempRect = new Rect();
+
+    private static final int[] CHECKED_STATE_SET = {
+        android.R.attr.state_checked
+    };
+
+    /**
+     * Construct a new Switch with default styling, overriding specific style
+     * attributes as requested.
+     *
+     * @param context The Context that will determine this widget's theming.
+     * @param attrs Specification of attributes that should deviate from default styling.
+     */
+    public Switch(Context context, AttributeSet attrs) {
+        this(context, attrs, R.attr.switchStyle);
+    }
+
+    /**
+     * Construct a new Switch with a default style determined by the given theme attribute,
+     * overriding specific style attributes as requested.
+     *
+     * @param context The Context that will determine this widget's theming.
+     * @param attrs Specification of attributes that should deviate from the default styling.
+     * @param defStyle An attribute ID within the active theme containing a reference to the
+     *                 default style for this widget. e.g. android.R.attr.switchStyle.
+     */
+    public Switch(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
+        Resources res = getResources();
+        DisplayMetrics dm = res.getDisplayMetrics();
+        mTextPaint.density = dm.density;
+        mThumbDrawable = res.getDrawable(R.drawable.switch_inner_holo_dark);
+        mTrackDrawable = res.getDrawable(R.drawable.switch_track_holo_dark);
+        mTextOn = res.getString(R.string.capital_on);
+        mTextOff = res.getString(R.string.capital_off);
+        mThumbTextPadding = res.getDimensionPixelSize(R.dimen.thumb_text_padding);
+        mSwitchMinWidth = res.getDimensionPixelSize(R.dimen.switch_min_width);
+        mSwitchTextMaxWidth = res.getDimensionPixelSize(R.dimen.switch_text_max_width);
+        mSwitchPadding = res.getDimensionPixelSize(R.dimen.switch_padding);
+        setSwitchTextAppearance(context, android.R.style.TextAppearance_Holo_Small);
+
+        ViewConfiguration config = ViewConfiguration.get(context);
+        mTouchSlop = config.getScaledTouchSlop();
+        mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
+
+        // Refresh display with current params
+        refreshDrawableState();
+        setChecked(isChecked());
+    }
+
+    /**
+     * Sets the switch text color, size, style, hint color, and highlight color
+     * from the specified TextAppearance resource.
+     */
+    public void setSwitchTextAppearance(Context context, int resid) {
+        Resources res = getResources();
+        mTextColors = getTextColors();
+        int ts = res.getDimensionPixelSize(R.dimen.thumb_text_size);
+        if (ts != mTextPaint.getTextSize()) {
+            mTextPaint.setTextSize(ts);
+            requestLayout();
+        }
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        if (mOnLayout == null) {
+            mOnLayout = makeLayout(mTextOn, mSwitchTextMaxWidth);
+        }
+        if (mOffLayout == null) {
+            mOffLayout = makeLayout(mTextOff, mSwitchTextMaxWidth);
+        }
+
+        mTrackDrawable.getPadding(mTempRect);
+        final int maxTextWidth = Math.min(mSwitchTextMaxWidth,
+                Math.max(mOnLayout.getWidth(), mOffLayout.getWidth()));
+        final int switchWidth = Math.max(mSwitchMinWidth,
+                maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
+        final int switchHeight = mTrackDrawable.getIntrinsicHeight();
+
+        mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
+
+        mSwitchWidth = switchWidth;
+        mSwitchHeight = switchHeight;
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        final int measuredHeight = getMeasuredHeight();
+        final int measuredWidth = getMeasuredWidth();
+        if (measuredHeight < switchHeight) {
+            setMeasuredDimension(measuredWidth, switchHeight);
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.onPopulateAccessibilityEvent(event);
+        CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
+        if (!TextUtils.isEmpty(text)) {
+            event.getText().add(text);
+        }
+    }
+
+    private Layout makeLayout(CharSequence text, int maxWidth) {
+        int actual_width = (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint));
+        StaticLayout l = new StaticLayout(text, 0, text.length(), mTextPaint,
+                actual_width,
+                Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true,
+                TextUtils.TruncateAt.END,
+                (int) Math.min(actual_width, maxWidth));
+        return l;
+    }
+
+    /**
+     * @return true if (x, y) is within the target area of the switch thumb
+     */
+    private boolean hitThumb(float x, float y) {
+        mThumbDrawable.getPadding(mTempRect);
+        final int thumbTop = mSwitchTop - mTouchSlop;
+        final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
+        final int thumbRight = thumbLeft + mThumbWidth +
+                mTempRect.left + mTempRect.right + mTouchSlop;
+        final int thumbBottom = mSwitchBottom + mTouchSlop;
+        return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mVelocityTracker.addMovement(ev);
+        final int action = ev.getActionMasked();
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                final float x = ev.getX();
+                final float y = ev.getY();
+                if (isEnabled() && hitThumb(x, y)) {
+                    mTouchMode = TOUCH_MODE_DOWN;
+                    mTouchX = x;
+                    mTouchY = y;
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                switch (mTouchMode) {
+                    case TOUCH_MODE_IDLE:
+                        // Didn't target the thumb, treat normally.
+                        break;
+
+                    case TOUCH_MODE_DOWN: {
+                        final float x = ev.getX();
+                        final float y = ev.getY();
+                        if (Math.abs(x - mTouchX) > mTouchSlop ||
+                                Math.abs(y - mTouchY) > mTouchSlop) {
+                            mTouchMode = TOUCH_MODE_DRAGGING;
+                            getParent().requestDisallowInterceptTouchEvent(true);
+                            mTouchX = x;
+                            mTouchY = y;
+                            return true;
+                        }
+                        break;
+                    }
+
+                    case TOUCH_MODE_DRAGGING: {
+                        final float x = ev.getX();
+                        final float dx = x - mTouchX;
+                        float newPos = Math.max(0,
+                                Math.min(mThumbPosition + dx, getThumbScrollRange()));
+                        if (newPos != mThumbPosition) {
+                            mThumbPosition = newPos;
+                            mTouchX = x;
+                            invalidate();
+                        }
+                        return true;
+                    }
+                }
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                if (mTouchMode == TOUCH_MODE_DRAGGING) {
+                    stopDrag(ev);
+                    return true;
+                }
+                mTouchMode = TOUCH_MODE_IDLE;
+                mVelocityTracker.clear();
+                break;
+            }
+        }
+
+        return super.onTouchEvent(ev);
+    }
+
+    private void cancelSuperTouch(MotionEvent ev) {
+        MotionEvent cancel = MotionEvent.obtain(ev);
+        cancel.setAction(MotionEvent.ACTION_CANCEL);
+        super.onTouchEvent(cancel);
+        cancel.recycle();
+    }
+
+    /**
+     * Called from onTouchEvent to end a drag operation.
+     *
+     * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
+     */
+    private void stopDrag(MotionEvent ev) {
+        mTouchMode = TOUCH_MODE_IDLE;
+        // Up and not canceled, also checks the switch has not been disabled during the drag
+        boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
+
+        cancelSuperTouch(ev);
+
+        if (commitChange) {
+            boolean newState;
+            mVelocityTracker.computeCurrentVelocity(1000);
+            float xvel = mVelocityTracker.getXVelocity();
+            if (Math.abs(xvel) > mMinFlingVelocity) {
+                newState = xvel > 0;
+            } else {
+                newState = getTargetCheckedState();
+            }
+            animateThumbToCheckedState(newState);
+        } else {
+            animateThumbToCheckedState(isChecked());
+        }
+    }
+
+    private void animateThumbToCheckedState(boolean newCheckedState) {
+        setChecked(newCheckedState);
+    }
+
+    private boolean getTargetCheckedState() {
+        return mThumbPosition >= getThumbScrollRange() / 2;
+    }
+
+    private void setThumbPosition(boolean checked) {
+        mThumbPosition = checked ? getThumbScrollRange() : 0;
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        super.setChecked(checked);
+        setThumbPosition(checked);
+        invalidate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        setThumbPosition(isChecked());
+
+        int switchRight;
+        int switchLeft;
+
+        switchRight = getWidth() - getPaddingRight();
+        switchLeft = switchRight - mSwitchWidth;
+
+        int switchTop = 0;
+        int switchBottom = 0;
+        switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
+            default:
+            case Gravity.TOP:
+                switchTop = getPaddingTop();
+                switchBottom = switchTop + mSwitchHeight;
+                break;
+
+            case Gravity.CENTER_VERTICAL:
+                switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
+                        mSwitchHeight / 2;
+                switchBottom = switchTop + mSwitchHeight;
+                break;
+
+            case Gravity.BOTTOM:
+                switchBottom = getHeight() - getPaddingBottom();
+                switchTop = switchBottom - mSwitchHeight;
+                break;
+        }
+
+        mSwitchLeft = switchLeft;
+        mSwitchTop = switchTop;
+        mSwitchBottom = switchBottom;
+        mSwitchRight = switchRight;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        // Draw the switch
+        int switchLeft = mSwitchLeft;
+        int switchTop = mSwitchTop;
+        int switchRight = mSwitchRight;
+        int switchBottom = mSwitchBottom;
+
+        mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+        mTrackDrawable.draw(canvas);
+
+        canvas.save();
+
+        mTrackDrawable.getPadding(mTempRect);
+        int switchInnerLeft = switchLeft + mTempRect.left;
+        int switchInnerTop = switchTop + mTempRect.top;
+        int switchInnerRight = switchRight - mTempRect.right;
+        int switchInnerBottom = switchBottom - mTempRect.bottom;
+        canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+
+        mThumbDrawable.getPadding(mTempRect);
+        final int thumbPos = (int) (mThumbPosition + 0.5f);
+        int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
+        int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
+
+        mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+        mThumbDrawable.draw(canvas);
+
+        // mTextColors should not be null, but just in case
+        if (mTextColors != null) {
+            mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
+                    mTextColors.getDefaultColor()));
+        }
+        mTextPaint.drawableState = getDrawableState();
+
+        Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
+
+        canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getEllipsizedWidth() / 2,
+                (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
+        switchText.draw(canvas);
+
+        canvas.restore();
+    }
+
+    @Override
+    public int getCompoundPaddingRight() {
+        int padding = super.getCompoundPaddingRight() + mSwitchWidth;
+        if (!TextUtils.isEmpty(getText())) {
+            padding += mSwitchPadding;
+        }
+        return padding;
+    }
+
+    private int getThumbScrollRange() {
+        if (mTrackDrawable == null) {
+            return 0;
+        }
+        mTrackDrawable.getPadding(mTempRect);
+        return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
+    }
+
+    @Override
+    protected int[] onCreateDrawableState(int extraSpace) {
+        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+
+        if (isChecked()) {
+            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
+        }
+        return drawableState;
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        super.drawableStateChanged();
+
+        int[] myDrawableState = getDrawableState();
+
+        // Set the state of the Drawable
+        // Drawable may be null when checked state is set from XML, from super constructor
+        if (mThumbDrawable != null) mThumbDrawable.setState(myDrawableState);
+        if (mTrackDrawable != null) mTrackDrawable.setState(myDrawableState);
+
+        invalidate();
+    }
+
+    @Override
+    protected boolean verifyDrawable(Drawable who) {
+        return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    @Override
+    public void jumpDrawablesToCurrentState() {
+        super.jumpDrawablesToCurrentState();
+        mThumbDrawable.jumpToCurrentState();
+        mTrackDrawable.jumpToCurrentState();
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setClassName(Switch.class.getName());
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setClassName(Switch.class.getName());
+        CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+        if (!TextUtils.isEmpty(switchText)) {
+            CharSequence oldText = info.getText();
+            if (TextUtils.isEmpty(oldText)) {
+                info.setText(switchText);
+            } else {
+                StringBuilder newText = new StringBuilder();
+                newText.append(oldText).append(' ').append(switchText);
+                info.setText(newText);
+            }
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/TimeIntervalPopup.java b/src/com/android/camera/ui/TimeIntervalPopup.java
new file mode 100644
index 0000000..18ad9f5
--- /dev/null
+++ b/src/com/android/camera/ui/TimeIntervalPopup.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.NumberPicker;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.camera.IconListPreference;
+import com.android.camera.ListPreference;
+import com.android.gallery3d.R;
+
+/**
+ * This is a popup window that allows users to turn on/off time lapse feature,
+ * and to select a time interval for taking a time lapse video.
+ */
+public class TimeIntervalPopup extends AbstractSettingPopup {
+    private static final String TAG = "TimeIntervalPopup";
+    private NumberPicker mNumberSpinner;
+    private NumberPicker mUnitSpinner;
+    private Switch mTimeLapseSwitch;
+    private final String[] mUnits;
+    private final String[] mDurations;
+    private IconListPreference mPreference;
+    private Listener mListener;
+    private Button mConfirmButton;
+    private TextView mHelpText;
+    private View mTimePicker;
+
+    static public interface Listener {
+        public void onListPrefChanged(ListPreference pref);
+    }
+
+    public void setSettingChangedListener(Listener listener) {
+        mListener = listener;
+    }
+
+    public TimeIntervalPopup(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        Resources res = context.getResources();
+        mUnits = res.getStringArray(R.array.pref_video_time_lapse_frame_interval_units);
+        mDurations = res
+                .getStringArray(R.array.pref_video_time_lapse_frame_interval_duration_values);
+    }
+
+    public void initialize(IconListPreference preference) {
+        mPreference = preference;
+
+        // Set title.
+        mTitle.setText(mPreference.getTitle());
+
+        // Duration
+        int durationCount = mDurations.length;
+        mNumberSpinner = (NumberPicker) findViewById(R.id.duration);
+        mNumberSpinner.setMinValue(0);
+        mNumberSpinner.setMaxValue(durationCount - 1);
+        mNumberSpinner.setDisplayedValues(mDurations);
+        mNumberSpinner.setWrapSelectorWheel(false);
+
+        // Units for duration (i.e. seconds, minutes, etc)
+        mUnitSpinner = (NumberPicker) findViewById(R.id.duration_unit);
+        mUnitSpinner.setMinValue(0);
+        mUnitSpinner.setMaxValue(mUnits.length - 1);
+        mUnitSpinner.setDisplayedValues(mUnits);
+        mUnitSpinner.setWrapSelectorWheel(false);
+
+        mTimePicker = findViewById(R.id.time_interval_picker);
+        mTimeLapseSwitch = (Switch) findViewById(R.id.time_lapse_switch);
+        mHelpText = (TextView) findViewById(R.id.set_time_interval_help_text);
+        mConfirmButton = (Button) findViewById(R.id.time_lapse_interval_set_button);
+
+        // Disable focus on the spinners to prevent keyboard from coming up
+        mNumberSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
+        mUnitSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
+
+        mTimeLapseSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                setTimeSelectionEnabled(isChecked);
+            }
+        });
+        mConfirmButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                updateInputState();
+            }
+        });
+    }
+
+    private void restoreSetting() {
+        int index = mPreference.findIndexOfValue(mPreference.getValue());
+        if (index == -1) {
+            Log.e(TAG, "Invalid preference value.");
+            mPreference.print();
+            throw new IllegalArgumentException();
+        } else if (index == 0) {
+            // default choice: time lapse off
+            mTimeLapseSwitch.setChecked(false);
+            setTimeSelectionEnabled(false);
+        } else {
+            mTimeLapseSwitch.setChecked(true);
+            setTimeSelectionEnabled(true);
+            int durationCount = mNumberSpinner.getMaxValue() + 1;
+            int unit = (index - 1) / durationCount;
+            int number = (index - 1) % durationCount;
+            mUnitSpinner.setValue(unit);
+            mNumberSpinner.setValue(number);
+        }
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        if (visibility == View.VISIBLE) {
+            if (getVisibility() != View.VISIBLE) {
+                // Set the number pickers and on/off switch to be consistent
+                // with the preference
+                restoreSetting();
+            }
+        }
+        super.setVisibility(visibility);
+    }
+
+    protected void setTimeSelectionEnabled(boolean enabled) {
+        mHelpText.setVisibility(enabled ? GONE : VISIBLE);
+        mTimePicker.setVisibility(enabled ? VISIBLE : GONE);
+    }
+
+    @Override
+    public void reloadPreference() {
+    }
+
+    private void updateInputState() {
+        if (mTimeLapseSwitch.isChecked()) {
+            int newId = mUnitSpinner.getValue() * (mNumberSpinner.getMaxValue() + 1)
+                    + mNumberSpinner.getValue() + 1;
+            mPreference.setValueIndex(newId);
+        } else {
+            mPreference.setValueIndex(0);
+        }
+
+        if (mListener != null) {
+            mListener.onListPrefChanged(mPreference);
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/TwoStateImageView.java b/src/com/android/camera/ui/TwoStateImageView.java
new file mode 100644
index 0000000..cd5b27f
--- /dev/null
+++ b/src/com/android/camera/ui/TwoStateImageView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 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.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * A @{code ImageView} which change the opacity of the icon if disabled.
+ */
+public class TwoStateImageView extends ImageView {
+    private static final int ENABLED_ALPHA = 255;
+    private static final int DISABLED_ALPHA = (int) (255 * 0.4);
+    private boolean mFilterEnabled = true;
+
+    public TwoStateImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public TwoStateImageView(Context context) {
+        this(context, null);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        if (mFilterEnabled) {
+            if (enabled) {
+                setAlpha(ENABLED_ALPHA);
+            } else {
+                setAlpha(DISABLED_ALPHA);
+            }
+        }
+    }
+
+    public void enableFilter(boolean enabled) {
+        mFilterEnabled = enabled;
+    }
+}
diff --git a/src/com/android/camera/ui/ZoomRenderer.java b/src/com/android/camera/ui/ZoomRenderer.java
new file mode 100644
index 0000000..86b82b4
--- /dev/null
+++ b/src/com/android/camera/ui/ZoomRenderer.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2012 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.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.view.ScaleGestureDetector;
+
+import com.android.gallery3d.R;
+
+public class ZoomRenderer extends OverlayRenderer
+        implements ScaleGestureDetector.OnScaleGestureListener {
+
+    private static final String TAG = "CAM_Zoom";
+
+    private int mMaxZoom;
+    private int mMinZoom;
+    private OnZoomChangedListener mListener;
+
+    private ScaleGestureDetector mDetector;
+    private Paint mPaint;
+    private Paint mTextPaint;
+    private int mCircleSize;
+    private int mCenterX;
+    private int mCenterY;
+    private float mMaxCircle;
+    private float mMinCircle;
+    private int mInnerStroke;
+    private int mOuterStroke;
+    private int mZoomSig;
+    private int mZoomFraction;
+    private Rect mTextBounds;
+
+    public interface OnZoomChangedListener {
+        void onZoomStart();
+        void onZoomEnd();
+        void onZoomValueChanged(int index);  // only for immediate zoom
+    }
+
+    public ZoomRenderer(Context ctx) {
+        Resources res = ctx.getResources();
+        mPaint = new Paint();
+        mPaint.setAntiAlias(true);
+        mPaint.setColor(Color.WHITE);
+        mPaint.setStyle(Paint.Style.STROKE);
+        mTextPaint = new Paint(mPaint);
+        mTextPaint.setStyle(Paint.Style.FILL);
+        mTextPaint.setTextSize(res.getDimensionPixelSize(R.dimen.zoom_font_size));
+        mTextPaint.setTextAlign(Paint.Align.LEFT);
+        mTextPaint.setAlpha(192);
+        mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
+        mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
+        mDetector = new ScaleGestureDetector(ctx, this);
+        mMinCircle = res.getDimensionPixelSize(R.dimen.zoom_ring_min);
+        mTextBounds = new Rect();
+        setVisible(false);
+    }
+
+    // set from module
+    public void setZoomMax(int zoomMaxIndex) {
+        mMaxZoom = zoomMaxIndex;
+        mMinZoom = 0;
+    }
+
+    public void setZoom(int index) {
+        mCircleSize = (int) (mMinCircle + index * (mMaxCircle - mMinCircle) / (mMaxZoom - mMinZoom));
+    }
+
+    public void setZoomValue(int value) {
+        value = value / 10;
+        mZoomSig = value / 10;
+        mZoomFraction = value % 10;
+    }
+
+    public void setOnZoomChangeListener(OnZoomChangedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void layout(int l, int t, int r, int b) {
+        super.layout(l, t, r, b);
+        mCenterX = (r - l) / 2;
+        mCenterY = (b - t) / 2;
+        mMaxCircle = Math.min(getWidth(), getHeight());
+        mMaxCircle = (mMaxCircle - mMinCircle) / 2;
+    }
+
+    public boolean isScaling() {
+        return mDetector.isInProgress();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        mPaint.setStrokeWidth(mInnerStroke);
+        canvas.drawCircle(mCenterX, mCenterY, mMinCircle, mPaint);
+        canvas.drawCircle(mCenterX, mCenterY, mMaxCircle, mPaint);
+        canvas.drawLine(mCenterX - mMinCircle, mCenterY,
+                mCenterX - mMaxCircle - 4, mCenterY, mPaint);
+        mPaint.setStrokeWidth(mOuterStroke);
+        canvas.drawCircle((float) mCenterX, (float) mCenterY,
+                (float) mCircleSize, mPaint);
+        String txt = mZoomSig+"."+mZoomFraction+"x";
+        mTextPaint.getTextBounds(txt, 0, txt.length(), mTextBounds);
+        canvas.drawText(txt, mCenterX - mTextBounds.centerX(), mCenterY - mTextBounds.centerY(),
+                mTextPaint);
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        final float sf = detector.getScaleFactor();
+        float circle = (int) (mCircleSize * sf * sf);
+        circle = Math.max(mMinCircle, circle);
+        circle = Math.min(mMaxCircle, circle);
+        if (mListener != null && (int) circle != mCircleSize) {
+            mCircleSize = (int) circle;
+            int zoom = mMinZoom + (int) ((mCircleSize - mMinCircle) * (mMaxZoom - mMinZoom) / (mMaxCircle - mMinCircle));
+            mListener.onZoomValueChanged(zoom);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        setVisible(true);
+        if (mListener != null) {
+            mListener.onZoomStart();
+        }
+        update();
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        setVisible(false);
+        if (mListener != null) {
+            mListener.onZoomEnd();
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/anim/AlphaAnimation.java b/src/com/android/gallery3d/anim/AlphaAnimation.java
index cb17527..f9f4cbd 100644
--- a/src/com/android/gallery3d/anim/AlphaAnimation.java
+++ b/src/com/android/gallery3d/anim/AlphaAnimation.java
@@ -17,7 +17,7 @@
 package com.android.gallery3d.anim;
 
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 public class AlphaAnimation extends CanvasAnimation {
     private final float mStartAlpha;
diff --git a/src/com/android/gallery3d/anim/CanvasAnimation.java b/src/com/android/gallery3d/anim/CanvasAnimation.java
index 4c8bcc8..cdc66c6 100644
--- a/src/com/android/gallery3d/anim/CanvasAnimation.java
+++ b/src/com/android/gallery3d/anim/CanvasAnimation.java
@@ -16,7 +16,7 @@
 
 package com.android.gallery3d.anim;
 
-import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 public abstract class CanvasAnimation extends Animation {
 
diff --git a/src/com/android/gallery3d/anim/StateTransitionAnimation.java b/src/com/android/gallery3d/anim/StateTransitionAnimation.java
index cf04d2c..bf8a544 100644
--- a/src/com/android/gallery3d/anim/StateTransitionAnimation.java
+++ b/src/com/android/gallery3d/anim/StateTransitionAnimation.java
@@ -20,9 +20,9 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
-import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
 import com.android.gallery3d.ui.GLView;
-import com.android.gallery3d.ui.RawTexture;
 import com.android.gallery3d.ui.TiledScreenNail;
 
 public class StateTransitionAnimation extends Animation {
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index 88ac028..ac39aa5 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -20,14 +20,17 @@
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.Window;
@@ -35,13 +38,13 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLRootView;
-import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 public class AbstractGalleryActivity extends Activity implements GalleryContext {
     @SuppressWarnings("unused")
@@ -71,6 +74,7 @@
         getWindow().setBackgroundDrawable(null);
         mPanoramaViewHelper = new PanoramaViewHelper(this);
         mPanoramaViewHelper.onCreate();
+        doBindBatchService();
     }
 
     @Override
@@ -218,16 +222,10 @@
         } finally {
             mGLRootView.unlockRenderThread();
         }
-        clearBitmapPool(MediaItem.getMicroThumbPool());
-        clearBitmapPool(MediaItem.getThumbPool());
-
+        GalleryBitmapPool.getInstance().clear();
         MediaItem.getBytesBufferPool().clear();
     }
 
-    private static void clearBitmapPool(BitmapPool pool) {
-        if (pool != null) pool.clear();
-    }
-
     @Override
     protected void onDestroy() {
         super.onDestroy();
@@ -237,6 +235,7 @@
         } finally {
             mGLRootView.unlockRenderThread();
         }
+        doUnbindBatchService();
     }
 
     @Override
@@ -308,4 +307,37 @@
         return (getWindow().getAttributes().flags
                 & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
     }
+
+    private BatchService mBatchService;
+    private boolean mBatchServiceIsBound = false;
+    private ServiceConnection mBatchServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mBatchService = ((BatchService.LocalBinder)service).getService();
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mBatchService = null;
+        }
+    };
+
+    private void doBindBatchService() {
+        bindService(new Intent(this, BatchService.class), mBatchServiceConnection, Context.BIND_AUTO_CREATE);
+        mBatchServiceIsBound = true;
+    }
+
+    private void doUnbindBatchService() {
+        if (mBatchServiceIsBound) {
+            // Detach our existing connection.
+            unbindService(mBatchServiceConnection);
+            mBatchServiceIsBound = false;
+        }
+    }
+
+    public ThreadPool getBatchServiceThreadPoolIfAvailable() {
+        if (mBatchServiceIsBound && mBatchService != null) {
+            return mBatchService.getThreadPool();
+        } else {
+            throw new RuntimeException("Batch service unavailable");
+        }
+    }
 }
diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java
index cdd91ff..2f1e0c9 100644
--- a/src/com/android/gallery3d/app/ActivityState.java
+++ b/src/com/android/gallery3d/app/ActivityState.java
@@ -19,15 +19,13 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.os.BatteryManager;
 import android.os.Bundle;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
+import android.view.HapticFeedbackConstants;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -36,9 +34,9 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.anim.StateTransitionAnimation;
+import com.android.gallery3d.glrenderer.RawTexture;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.PreparePageFadeoutTexture;
-import com.android.gallery3d.ui.RawTexture;
 import com.android.gallery3d.util.GalleryUtils;
 
 abstract public class ActivityState {
@@ -62,9 +60,6 @@
         public Intent resultData;
     }
 
-    protected boolean mHapticsEnabled;
-    private ContentResolver mContentResolver;
-
     private boolean mDestroyed = false;
     private boolean mPlugged = false;
     boolean mIsFinishing = false;
@@ -92,7 +87,6 @@
     void initialize(AbstractGalleryActivity activity, Bundle data) {
         mActivity = activity;
         mData = data;
-        mContentResolver = activity.getAndroidContext().getContentResolver();
     }
 
     public Bundle getData() {
@@ -175,15 +169,20 @@
 
     protected void transitionOnNextPause(Class<? extends ActivityState> outgoing,
             Class<? extends ActivityState> incoming, StateTransitionAnimation.Transition hint) {
-        if (outgoing == PhotoPage.class && incoming == AlbumPage.class) {
+        if (outgoing == SinglePhotoPage.class && incoming == AlbumPage.class) {
             mNextTransition = StateTransitionAnimation.Transition.Outgoing;
-        } else if (outgoing == AlbumPage.class && incoming == PhotoPage.class) {
+        } else if (outgoing == AlbumPage.class && incoming == SinglePhotoPage.class) {
             mNextTransition = StateTransitionAnimation.Transition.PhotoIncoming;
         } else {
             mNextTransition = hint;
         }
     }
 
+    protected void performHapticFeedback(int feedbackConstant) {
+        mActivity.getWindow().getDecorView().performHapticFeedback(feedbackConstant,
+                HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+    }
+
     protected void onPause() {
         if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) {
             ((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver);
@@ -231,13 +230,6 @@
             activity.registerReceiver(mPowerIntentReceiver, filter);
         }
 
-        try {
-            mHapticsEnabled = Settings.System.getInt(mContentResolver,
-                    Settings.System.HAPTIC_FEEDBACK_ENABLED) != 0;
-        } catch (SettingNotFoundException e) {
-            mHapticsEnabled = false;
-        }
-
         onResume();
 
         // the transition store should be cleared after resume;
diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java
index 0ee1b03..28a8228 100644
--- a/src/com/android/gallery3d/app/AlbumDataLoader.java
+++ b/src/com/android/gallery3d/app/AlbumDataLoader.java
@@ -120,8 +120,7 @@
 
     public MediaItem get(int index) {
         if (!isActive(index)) {
-            throw new IllegalArgumentException(String.format(
-                    "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
+            return mSource.getMediaItem(index, 1).get(0);
         }
         return mData[index % mData.length];
     }
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index ee7a107..001ce87 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -24,8 +24,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.os.Vibrator;
 import android.provider.MediaStore;
+import android.view.HapticFeedbackConstants;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -38,15 +38,16 @@
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.MtpDevice;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.glrenderer.FadeTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.ui.ActionModeHandler;
 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
 import com.android.gallery3d.ui.AlbumSlotRenderer;
 import com.android.gallery3d.ui.DetailsHelper;
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
-import com.android.gallery3d.ui.FadeTexture;
-import com.android.gallery3d.ui.GLCanvas;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.PhotoFallbackEffect;
@@ -58,6 +59,7 @@
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.MediaSetUtils;
 
+
 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
         SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener {
     @SuppressWarnings("unused")
@@ -89,7 +91,6 @@
     private AlbumDataLoader mAlbumDataAdapter;
 
     protected SelectionManager mSelectionManager;
-    private Vibrator mVibrator;
 
     private boolean mGetContent;
     private boolean mShowClusterMenu;
@@ -304,10 +305,10 @@
                     startInFilmstrip);
             data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll());
             if (startInFilmstrip) {
-                mActivity.getStateManager().switchState(this, PhotoPage.class, data);
+                mActivity.getStateManager().switchState(this, FilmstripPage.class, data);
             } else {
                 mActivity.getStateManager().startStateForResult(
-                            PhotoPage.class, REQUEST_PHOTO, data);
+                            SinglePhotoPage.class, REQUEST_PHOTO, data);
             }
         }
     }
@@ -316,13 +317,12 @@
         DataManager dm = mActivity.getDataManager();
         Activity activity = mActivity;
         if (mData.getString(Gallery.EXTRA_CROP) != null) {
-            // TODO: Handle MtpImagew
             Uri uri = dm.getContentUri(item.getPath());
-            Intent intent = new Intent(CropImage.ACTION_CROP, uri)
+            Intent intent = new Intent(FilterShowActivity.CROP_ACTION, uri)
                     .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
                     .putExtras(getData());
             if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
-                intent.putExtra(CropImage.KEY_RETURN_DATA, true);
+                intent.putExtra(CropExtras.KEY_RETURN_DATA, true);
             }
             activity.startActivity(intent);
             activity.finish();
@@ -371,15 +371,13 @@
         mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
         mDetailsSource = new MyDetailsSource();
         Context context = mActivity.getAndroidContext();
-        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
 
-        // Enable auto-select-all for mtp album
         if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
             mSelectionManager.selectAll();
         }
 
         mLaunchedFromPhotoPage =
-                mActivity.getStateManager().hasStateClass(PhotoPage.class);
+                mActivity.getStateManager().hasStateClass(FilmstripPage.class);
         mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false);
 
         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
@@ -443,7 +441,7 @@
             mSelectionManager.leaveSelectionMode();
         }
         mAlbumView.setSlotFilter(null);
-
+        mActionModeHandler.pause();
         mAlbumDataAdapter.pause();
         mAlbumView.pause();
         DetailsHelper.pause();
@@ -456,7 +454,6 @@
             mSyncTask = null;
             clearLoadingBit(BIT_LOADING_SYNC);
         }
-        mActionModeHandler.pause();
     }
 
     @Override
@@ -465,6 +462,7 @@
         if (mAlbumDataAdapter != null) {
             mAlbumDataAdapter.setLoadingListener(null);
         }
+        mActionModeHandler.destroy();
     }
 
     private void initializeViews() {
@@ -553,9 +551,6 @@
             inflator.inflate(R.menu.album, menu);
             actionBar.setTitle(mMediaSet.getName());
 
-            menu.findItem(R.id.action_slideshow)
-                    .setVisible(!(mMediaSet instanceof MtpDevice));
-
             FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
 
             menu.findItem(R.id.action_group_by).setVisible(mShowClusterMenu);
@@ -662,7 +657,7 @@
         switch (mode) {
             case SelectionManager.ENTER_SELECTION_MODE: {
                 mActionModeHandler.startActionMode();
-                if (mHapticsEnabled) mVibrator.vibrate(100);
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                 break;
             }
             case SelectionManager.LEAVE_SELECTION_MODE: {
diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java
index cae606b..4708e6f 100644
--- a/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -23,7 +23,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.os.Vibrator;
+import android.view.HapticFeedbackConstants;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -41,6 +41,8 @@
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.FadeTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.settings.GallerySettings;
 import com.android.gallery3d.ui.ActionModeHandler;
@@ -48,8 +50,6 @@
 import com.android.gallery3d.ui.AlbumSetSlotRenderer;
 import com.android.gallery3d.ui.DetailsHelper;
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
-import com.android.gallery3d.ui.FadeTexture;
-import com.android.gallery3d.ui.GLCanvas;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.SelectionManager;
@@ -92,7 +92,6 @@
     private boolean mShowClusterMenu;
     private GalleryActionBar mActionBar;
     private int mSelectedAction;
-    private Vibrator mVibrator;
 
     protected SelectionManager mSelectionManager;
     private AlbumSetDataLoader mAlbumSetDataAdapter;
@@ -263,10 +262,7 @@
             mActivity.getStateManager().startStateForResult(
                     AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
         } else {
-            if (!mGetContent && (targetSet.getSupportedOperations()
-                    & MediaObject.SUPPORT_IMPORT) != 0) {
-                data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true);
-            } else if (!mGetContent && albumShouldOpenInFilmstrip(targetSet)) {
+            if (!mGetContent && albumShouldOpenInFilmstrip(targetSet)) {
                 data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
                         mSlotView.getSlotRect(slotIndex, mRootPane));
                 data.putInt(PhotoPage.KEY_INDEX_HINT, 0);
@@ -275,7 +271,7 @@
                 data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, true);
                 data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, targetSet.isCameraRoll());
                 mActivity.getStateManager().startStateForResult(
-                        PhotoPage.class, AlbumPage.REQUEST_PHOTO, data);
+                        FilmstripPage.class, AlbumPage.REQUEST_PHOTO, data);
                 return;
             }
             data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath);
@@ -332,7 +328,6 @@
         mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE);
         mEyePosition = new EyePosition(context, this);
         mDetailsSource = new MyDetailsSource();
-        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
         mActionBar = mActivity.getGalleryActionBar();
         mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE,
                 FilterUtils.CLUSTER_BY_ALBUM);
@@ -353,8 +348,9 @@
 
     @Override
     public void onDestroy() {
-        cleanupCameraButton();
         super.onDestroy();
+        cleanupCameraButton();
+        mActionModeHandler.destroy();
     }
 
     private boolean setupCameraButton() {
@@ -439,9 +435,9 @@
     public void onPause() {
         super.onPause();
         mIsActive = false;
-        mActionModeHandler.pause();
         mAlbumSetDataAdapter.pause();
         mAlbumSetView.pause();
+        mActionModeHandler.pause();
         mEyePosition.pause();
         DetailsHelper.pause();
         // Call disableClusterMenu to avoid receiving callback after paused.
@@ -655,7 +651,7 @@
             case SelectionManager.ENTER_SELECTION_MODE: {
                 mActionBar.disableClusterMenu(true);
                 mActionModeHandler.startActionMode();
-                if (mHapticsEnabled) mVibrator.vibrate(100);
+                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
                 break;
             }
             case SelectionManager.LEAVE_SELECTION_MODE: {
diff --git a/src/com/android/gallery3d/app/BatchService.java b/src/com/android/gallery3d/app/BatchService.java
new file mode 100644
index 0000000..564001d
--- /dev/null
+++ b/src/com/android/gallery3d/app/BatchService.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.app;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+import com.android.gallery3d.util.ThreadPool;
+
+public class BatchService extends Service {
+
+    public class LocalBinder extends Binder {
+        BatchService getService() {
+            return BatchService.this;
+        }
+    }
+
+    private final IBinder mBinder = new LocalBinder();
+    private ThreadPool mThreadPool = new ThreadPool(1, 1);
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    // The threadpool returned by getThreadPool must have only 1 thread
+    // running at a time, as MenuExecutor (atrociously) depends on this
+    // guarantee for synchronization.
+    public ThreadPool getThreadPool() {
+        return mThreadPool;
+    }
+}
diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java
index ab43dad..a4f5807 100644
--- a/src/com/android/gallery3d/app/CommonControllerOverlay.java
+++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java
@@ -17,6 +17,7 @@
 package com.android.gallery3d.app;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.view.Gravity;
 import android.view.KeyEvent;
@@ -83,7 +84,8 @@
         // multiple ones for trimming.
         createTimeBar(context);
         addView(mTimeBar, wrapContent);
-
+        mTimeBar.setContentDescription(
+                context.getResources().getString(R.string.accessibility_time_bar));
         mLoadingView = new LinearLayout(context);
         mLoadingView.setOrientation(LinearLayout.VERTICAL);
         mLoadingView.setGravity(Gravity.CENTER_HORIZONTAL);
@@ -97,6 +99,8 @@
 
         mPlayPauseReplayView = new ImageView(context);
         mPlayPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play);
+        mPlayPauseReplayView.setContentDescription(
+                context.getResources().getString(R.string.accessibility_play_video));
         mPlayPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol);
         mPlayPauseReplayView.setScaleType(ScaleType.CENTER);
         mPlayPauseReplayView.setFocusable(true);
@@ -154,7 +158,7 @@
     @Override
     public void showEnded() {
         mState = State.ENDED;
-        showMainView(mPlayPauseReplayView);
+        if (mCanReplay) showMainView(mPlayPauseReplayView);
     }
 
     @Override
@@ -274,10 +278,6 @@
         mBackground.layout(0, y - mTimeBar.getBarHeight(), w, y);
         mTimeBar.layout(pl, y - mTimeBar.getPreferredHeight(), w - pr, y);
 
-        // Needed, otherwise the framework will not re-layout in case only the
-        // padding is changed
-        mTimeBar.requestLayout();
-
         // Put the play/pause/next/ previous button in the center of the screen
         layoutCenteredView(mPlayPauseReplayView, 0, 0, w, h);
 
@@ -303,10 +303,19 @@
     protected void updateViews() {
         mBackground.setVisibility(View.VISIBLE);
         mTimeBar.setVisibility(View.VISIBLE);
-        mPlayPauseReplayView.setImageResource(
-                mState == State.PAUSED ? R.drawable.ic_vidcontrol_play :
-                mState == State.PLAYING ? R.drawable.ic_vidcontrol_pause :
-                R.drawable.ic_vidcontrol_reload);
+        Resources resources = getContext().getResources();
+        int imageResource = R.drawable.ic_vidcontrol_reload;
+        String contentDescription = resources.getString(R.string.accessibility_reload_video);
+        if (mState == State.PAUSED) {
+            imageResource = R.drawable.ic_vidcontrol_play;
+            contentDescription = resources.getString(R.string.accessibility_play_video);
+        } else if (mState == State.PLAYING) {
+            imageResource = R.drawable.ic_vidcontrol_pause;
+            contentDescription = resources.getString(R.string.accessibility_pause_video);
+        }
+
+        mPlayPauseReplayView.setImageResource(imageResource);
+        mPlayPauseReplayView.setContentDescription(contentDescription);
         mPlayPauseReplayView.setVisibility(
                 (mState != State.LOADING && mState != State.ERROR &&
                 !(mState == State.ENDED && !mCanReplay))
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java
deleted file mode 100644
index 89ca63d..0000000
--- a/src/com/android/gallery3d/app/CropImage.java
+++ /dev/null
@@ -1,1040 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.app;
-
-import android.annotation.TargetApi;
-import android.app.ActionBar;
-import android.app.ProgressDialog;
-import android.app.WallpaperManager;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.BitmapRegionDecoder;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images;
-import android.util.FloatMath;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.Toast;
-
-import com.android.camera.Util;
-import com.android.gallery3d.R;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.LocalImage;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.exif.ExifData;
-import com.android.gallery3d.exif.ExifOutputStream;
-import com.android.gallery3d.exif.ExifReader;
-import com.android.gallery3d.exif.ExifTag;
-import com.android.gallery3d.picasasource.PicasaSource;
-import com.android.gallery3d.ui.BitmapScreenNail;
-import com.android.gallery3d.ui.BitmapTileProvider;
-import com.android.gallery3d.ui.CropView;
-import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.SynchronizedHandler;
-import com.android.gallery3d.ui.TileImageViewAdapter;
-import com.android.gallery3d.util.BucketNames;
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.FutureListener;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.InterruptableOutputStream;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteOrder;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-/**
- * The activity can crop specific region of interest from an image.
- */
-public class CropImage extends AbstractGalleryActivity {
-    private static final String TAG = "CropImage";
-    public static final String ACTION_CROP = "com.android.camera.action.CROP";
-
-    private static final int MAX_PIXEL_COUNT = 5 * 1000000; // 5M pixels
-    private static final int MAX_FILE_INDEX = 1000;
-    private static final int TILE_SIZE = 512;
-    private static final int BACKUP_PIXEL_COUNT = 480000; // around 800x600
-
-    private static final int MSG_LARGE_BITMAP = 1;
-    private static final int MSG_BITMAP = 2;
-    private static final int MSG_SAVE_COMPLETE = 3;
-    private static final int MSG_SHOW_SAVE_ERROR = 4;
-    private static final int MSG_CANCEL_DIALOG = 5;
-
-    private static final int MAX_BACKUP_IMAGE_SIZE = 320;
-    private static final int DEFAULT_COMPRESS_QUALITY = 90;
-    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
-
-    public static final String KEY_RETURN_DATA = "return-data";
-    public static final String KEY_CROPPED_RECT = "cropped-rect";
-    public static final String KEY_ASPECT_X = "aspectX";
-    public static final String KEY_ASPECT_Y = "aspectY";
-    public static final String KEY_SPOTLIGHT_X = "spotlightX";
-    public static final String KEY_SPOTLIGHT_Y = "spotlightY";
-    public static final String KEY_OUTPUT_X = "outputX";
-    public static final String KEY_OUTPUT_Y = "outputY";
-    public static final String KEY_SCALE = "scale";
-    public static final String KEY_DATA = "data";
-    public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
-    public static final String KEY_OUTPUT_FORMAT = "outputFormat";
-    public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
-    public static final String KEY_NO_FACE_DETECTION = "noFaceDetection";
-    public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
-
-    private static final String KEY_STATE = "state";
-
-    private static final int STATE_INIT = 0;
-    private static final int STATE_LOADED = 1;
-    private static final int STATE_SAVING = 2;
-
-    public static final File DOWNLOAD_BUCKET = new File(
-            Environment.getExternalStorageDirectory(), BucketNames.DOWNLOAD);
-
-    public static final String CROP_ACTION = "com.android.camera.action.CROP";
-
-    private int mState = STATE_INIT;
-
-    private CropView mCropView;
-
-    private boolean mDoFaceDetection = true;
-
-    private Handler mMainHandler;
-
-    // We keep the following members so that we can free them
-
-    // mBitmap is the unrotated bitmap we pass in to mCropView for detect faces.
-    // mCropView is responsible for rotating it to the way that it is viewed by users.
-    private Bitmap mBitmap;
-    private BitmapTileProvider mBitmapTileProvider;
-    private BitmapRegionDecoder mRegionDecoder;
-    private Bitmap mBitmapInIntent;
-    private boolean mUseRegionDecoder = false;
-    private BitmapScreenNail mBitmapScreenNail;
-
-    private ProgressDialog mProgressDialog;
-    private Future<BitmapRegionDecoder> mLoadTask;
-    private Future<Bitmap> mLoadBitmapTask;
-    private Future<Intent> mSaveTask;
-
-    private MediaItem mMediaItem;
-
-    @Override
-    public void onCreate(Bundle bundle) {
-        super.onCreate(bundle);
-        requestWindowFeature(Window.FEATURE_ACTION_BAR);
-        requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
-
-        // Initialize UI
-        setContentView(R.layout.cropimage);
-        mCropView = new CropView(this);
-        getGLRoot().setContentPane(mCropView);
-
-        ActionBar actionBar = getActionBar();
-        int displayOptions = ActionBar.DISPLAY_HOME_AS_UP
-                | ActionBar.DISPLAY_SHOW_TITLE;
-        actionBar.setDisplayOptions(displayOptions, displayOptions);
-
-        Bundle extra = getIntent().getExtras();
-        if (extra != null) {
-            if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) {
-                actionBar.setTitle(getString(R.string.set_wallpaper));
-            }
-            if (extra.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
-                getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
-            }
-        }
-
-        mMainHandler = new SynchronizedHandler(getGLRoot()) {
-            @Override
-            public void handleMessage(Message message) {
-                switch (message.what) {
-                    case MSG_LARGE_BITMAP: {
-                        dismissProgressDialogIfShown();
-                        onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj);
-                        break;
-                    }
-                    case MSG_BITMAP: {
-                        dismissProgressDialogIfShown();
-                        onBitmapAvailable((Bitmap) message.obj);
-                        break;
-                    }
-                    case MSG_SHOW_SAVE_ERROR: {
-                        dismissProgressDialogIfShown();
-                        setResult(RESULT_CANCELED);
-                        Toast.makeText(CropImage.this,
-                                CropImage.this.getString(R.string.save_error),
-                                Toast.LENGTH_LONG).show();
-                        finish();
-                    }
-                    case MSG_SAVE_COMPLETE: {
-                        dismissProgressDialogIfShown();
-                        setResult(RESULT_OK, (Intent) message.obj);
-                        finish();
-                        break;
-                    }
-                    case MSG_CANCEL_DIALOG: {
-                        setResult(RESULT_CANCELED);
-                        finish();
-                        break;
-                    }
-                }
-            }
-        };
-
-        setCropParameters();
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle saveState) {
-        saveState.putInt(KEY_STATE, mState);
-    }
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        super.onCreateOptionsMenu(menu);
-        getMenuInflater().inflate(R.menu.crop, menu);
-        return true;
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case android.R.id.home: {
-                finish();
-                break;
-            }
-            case R.id.cancel: {
-                setResult(RESULT_CANCELED);
-                finish();
-                break;
-            }
-            case R.id.save: {
-                onSaveClicked();
-                break;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public void onBackPressed() {
-        finish();
-    }
-
-    private class SaveOutput implements Job<Intent> {
-        private final RectF mCropRect;
-
-        public SaveOutput(RectF cropRect) {
-            mCropRect = cropRect;
-        }
-
-        @Override
-        public Intent run(JobContext jc) {
-            RectF cropRect = mCropRect;
-            Bundle extra = getIntent().getExtras();
-
-            Rect rect = new Rect(
-                    Math.round(cropRect.left), Math.round(cropRect.top),
-                    Math.round(cropRect.right), Math.round(cropRect.bottom));
-
-            Intent result = new Intent();
-            result.putExtra(KEY_CROPPED_RECT, rect);
-            Bitmap cropped = null;
-            boolean outputted = false;
-            if (extra != null) {
-                Uri uri = (Uri) extra.getParcelable(MediaStore.EXTRA_OUTPUT);
-                if (uri != null) {
-                    if (jc.isCancelled()) return null;
-                    outputted = true;
-                    cropped = getCroppedImage(rect);
-                    if (!saveBitmapToUri(jc, cropped, uri)) return null;
-                }
-                if (extra.getBoolean(KEY_RETURN_DATA, false)) {
-                    if (jc.isCancelled()) return null;
-                    outputted = true;
-                    if (cropped == null) cropped = getCroppedImage(rect);
-                    result.putExtra(KEY_DATA, cropped);
-                }
-                if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) {
-                    if (jc.isCancelled()) return null;
-                    outputted = true;
-                    if (cropped == null) cropped = getCroppedImage(rect);
-                    if (!setAsWallpaper(jc, cropped)) return null;
-                }
-            }
-            if (!outputted) {
-                if (jc.isCancelled()) return null;
-                if (cropped == null) cropped = getCroppedImage(rect);
-                Uri data = saveToMediaProvider(jc, cropped);
-                if (data != null) result.setData(data);
-            }
-            return result;
-        }
-    }
-
-    public static String determineCompressFormat(MediaObject obj) {
-        String compressFormat = "JPEG";
-        if (obj instanceof MediaItem) {
-            String mime = ((MediaItem) obj).getMimeType();
-            if (mime.contains("png") || mime.contains("gif")) {
-              // Set the compress format to PNG for png and gif images
-              // because they may contain alpha values.
-              compressFormat = "PNG";
-            }
-        }
-        return compressFormat;
-    }
-
-    private boolean setAsWallpaper(JobContext jc, Bitmap wallpaper) {
-        try {
-            WallpaperManager.getInstance(this).setBitmap(wallpaper);
-        } catch (IOException e) {
-            Log.w(TAG, "fail to set wall paper", e);
-        }
-        return true;
-    }
-
-    private File saveMedia(
-            JobContext jc, Bitmap cropped, File directory, String filename, ExifData exifData) {
-        // Try file-1.jpg, file-2.jpg, ... until we find a filename
-        // which does not exist yet.
-        File candidate = null;
-        String fileExtension = getFileExtension();
-        for (int i = 1; i < MAX_FILE_INDEX; ++i) {
-            candidate = new File(directory, filename + "-" + i + "."
-                    + fileExtension);
-            try {
-                if (candidate.createNewFile()) break;
-            } catch (IOException e) {
-                Log.e(TAG, "fail to create new file: "
-                        + candidate.getAbsolutePath(), e);
-                return null;
-            }
-        }
-        if (!candidate.exists() || !candidate.isFile()) {
-            throw new RuntimeException("cannot create file: " + filename);
-        }
-
-        candidate.setReadable(true, false);
-        candidate.setWritable(true, false);
-
-        try {
-            FileOutputStream fos = new FileOutputStream(candidate);
-            try {
-                if (exifData != null) {
-                    ExifOutputStream eos = new ExifOutputStream(fos);
-                    eos.setExifData(exifData);
-                    saveBitmapToOutputStream(jc, cropped,
-                            convertExtensionToCompressFormat(fileExtension), eos);
-                } else {
-                    saveBitmapToOutputStream(jc, cropped,
-                            convertExtensionToCompressFormat(fileExtension), fos);
-                }
-            } finally {
-                fos.close();
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "fail to save image: "
-                    + candidate.getAbsolutePath(), e);
-            candidate.delete();
-            return null;
-        }
-
-        if (jc.isCancelled()) {
-            candidate.delete();
-            return null;
-        }
-
-        return candidate;
-    }
-
-    private ExifData getExifData(String path) {
-        FileInputStream is = null;
-        try {
-            is = new FileInputStream(path);
-            ExifReader reader = new ExifReader();
-            ExifData data = reader.read(is);
-            return data;
-        } catch (Throwable t) {
-            Log.w(TAG, "Cannot read EXIF data", t);
-            return null;
-        } finally {
-            Util.closeSilently(is);
-        }
-    }
-
-    private static final String EXIF_SOFTWARE_VALUE = "Android Gallery";
-
-    private void changeExifData(ExifData data, int width, int height) {
-        data.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(width);
-        data.addTag(ExifTag.TAG_IMAGE_LENGTH).setValue(height);
-        data.addTag(ExifTag.TAG_SOFTWARE).setValue(EXIF_SOFTWARE_VALUE);
-        data.addTag(ExifTag.TAG_DATE_TIME).setTimeValue(System.currentTimeMillis());
-        // Remove the original thumbnail
-        // TODO: generate a new thumbnail for the cropped image.
-        data.removeThumbnailData();
-    }
-
-    private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) {
-        if (PicasaSource.isPicasaImage(mMediaItem)) {
-            return savePicasaImage(jc, cropped);
-        } else if (mMediaItem instanceof LocalImage) {
-            return saveLocalImage(jc, cropped);
-        } else {
-            return saveGenericImage(jc, cropped);
-        }
-    }
-
-    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
-    private static void setImageSize(ContentValues values, int width, int height) {
-        // The two fields are available since ICS but got published in JB
-        if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) {
-            values.put(Images.Media.WIDTH, width);
-            values.put(Images.Media.HEIGHT, height);
-        }
-    }
-
-    private Uri savePicasaImage(JobContext jc, Bitmap cropped) {
-        if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) {
-            throw new RuntimeException("cannot create download folder");
-        }
-        String filename = PicasaSource.getImageTitle(mMediaItem);
-        int pos = filename.lastIndexOf('.');
-        if (pos >= 0) filename = filename.substring(0, pos);
-        ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN);
-        PicasaSource.extractExifValues(mMediaItem, exifData);
-        changeExifData(exifData, cropped.getWidth(), cropped.getHeight());
-        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, exifData);
-        if (output == null) return null;
-
-        long now = System.currentTimeMillis() / 1000;
-        ContentValues values = new ContentValues();
-        values.put(Images.Media.TITLE, PicasaSource.getImageTitle(mMediaItem));
-        values.put(Images.Media.DISPLAY_NAME, output.getName());
-        values.put(Images.Media.DATE_TAKEN, PicasaSource.getDateTaken(mMediaItem));
-        values.put(Images.Media.DATE_MODIFIED, now);
-        values.put(Images.Media.DATE_ADDED, now);
-        values.put(Images.Media.MIME_TYPE, getOutputMimeType());
-        values.put(Images.Media.ORIENTATION, 0);
-        values.put(Images.Media.DATA, output.getAbsolutePath());
-        values.put(Images.Media.SIZE, output.length());
-        setImageSize(values, cropped.getWidth(), cropped.getHeight());
-
-        double latitude = PicasaSource.getLatitude(mMediaItem);
-        double longitude = PicasaSource.getLongitude(mMediaItem);
-        if (GalleryUtils.isValidLocation(latitude, longitude)) {
-            values.put(Images.Media.LATITUDE, latitude);
-            values.put(Images.Media.LONGITUDE, longitude);
-        }
-        return getContentResolver().insert(
-                Images.Media.EXTERNAL_CONTENT_URI, values);
-    }
-
-    private Uri saveLocalImage(JobContext jc, Bitmap cropped) {
-        LocalImage localImage = (LocalImage) mMediaItem;
-
-        File oldPath = new File(localImage.filePath);
-        File directory = new File(oldPath.getParent());
-
-        String filename = oldPath.getName();
-        int pos = filename.lastIndexOf('.');
-        if (pos >= 0) filename = filename.substring(0, pos);
-        File output = null;
-
-        ExifData exifData = null;
-        if (convertExtensionToCompressFormat(getFileExtension()) == CompressFormat.JPEG) {
-            exifData = getExifData(oldPath.getAbsolutePath());
-            if (exifData != null) {
-                changeExifData(exifData, cropped.getWidth(), cropped.getHeight());
-            }
-        }
-        output = saveMedia(jc, cropped, directory, filename, exifData);
-        if (output == null) return null;
-
-        long now = System.currentTimeMillis() / 1000;
-        ContentValues values = new ContentValues();
-        values.put(Images.Media.TITLE, localImage.caption);
-        values.put(Images.Media.DISPLAY_NAME, output.getName());
-        values.put(Images.Media.DATE_TAKEN, localImage.dateTakenInMs);
-        values.put(Images.Media.DATE_MODIFIED, now);
-        values.put(Images.Media.DATE_ADDED, now);
-        values.put(Images.Media.MIME_TYPE, getOutputMimeType());
-        values.put(Images.Media.ORIENTATION, 0);
-        values.put(Images.Media.DATA, output.getAbsolutePath());
-        values.put(Images.Media.SIZE, output.length());
-
-        setImageSize(values, cropped.getWidth(), cropped.getHeight());
-
-        if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) {
-            values.put(Images.Media.LATITUDE, localImage.latitude);
-            values.put(Images.Media.LONGITUDE, localImage.longitude);
-        }
-        return getContentResolver().insert(
-                Images.Media.EXTERNAL_CONTENT_URI, values);
-    }
-
-    private Uri saveGenericImage(JobContext jc, Bitmap cropped) {
-        if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) {
-            throw new RuntimeException("cannot create download folder");
-        }
-
-        long now = System.currentTimeMillis();
-        String filename = new SimpleDateFormat(TIME_STAMP_NAME).
-                format(new Date(now));
-
-        File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null);
-        if (output == null) return null;
-
-        ContentValues values = new ContentValues();
-        values.put(Images.Media.TITLE, filename);
-        values.put(Images.Media.DISPLAY_NAME, output.getName());
-        values.put(Images.Media.DATE_TAKEN, now);
-        values.put(Images.Media.DATE_MODIFIED, now / 1000);
-        values.put(Images.Media.DATE_ADDED, now / 1000);
-        values.put(Images.Media.MIME_TYPE, getOutputMimeType());
-        values.put(Images.Media.ORIENTATION, 0);
-        values.put(Images.Media.DATA, output.getAbsolutePath());
-        values.put(Images.Media.SIZE, output.length());
-
-        setImageSize(values, cropped.getWidth(), cropped.getHeight());
-
-        return getContentResolver().insert(
-                Images.Media.EXTERNAL_CONTENT_URI, values);
-    }
-
-    private boolean saveBitmapToOutputStream(
-            JobContext jc, Bitmap bitmap, CompressFormat format, OutputStream os) {
-        // We wrap the OutputStream so that it can be interrupted.
-        final InterruptableOutputStream ios = new InterruptableOutputStream(os);
-        jc.setCancelListener(new CancelListener() {
-                @Override
-                public void onCancel() {
-                    ios.interrupt();
-                }
-            });
-        try {
-            bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, ios);
-            return !jc.isCancelled();
-        } finally {
-            jc.setCancelListener(null);
-            Utils.closeSilently(ios);
-        }
-    }
-
-    private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) {
-        try {
-            OutputStream out = getContentResolver().openOutputStream(uri);
-            try {
-                return saveBitmapToOutputStream(jc, bitmap,
-                        convertExtensionToCompressFormat(getFileExtension()), out);
-            } finally {
-                Utils.closeSilently(out);
-            }
-        } catch (FileNotFoundException e) {
-            Log.w(TAG, "cannot write output", e);
-        }
-        return true;
-    }
-
-    private CompressFormat convertExtensionToCompressFormat(String extension) {
-        return extension.equals("png")
-                ? CompressFormat.PNG
-                : CompressFormat.JPEG;
-    }
-
-    private String getOutputMimeType() {
-        return getFileExtension().equals("png") ? "image/png" : "image/jpeg";
-    }
-
-    private String getFileExtension() {
-        String requestFormat = getIntent().getStringExtra(KEY_OUTPUT_FORMAT);
-        String outputFormat = (requestFormat == null)
-                ? determineCompressFormat(mMediaItem)
-                : requestFormat;
-
-        outputFormat = outputFormat.toLowerCase();
-        return (outputFormat.equals("png") || outputFormat.equals("gif"))
-                ? "png" // We don't support gif compression.
-                : "jpg";
-    }
-
-    private void onSaveClicked() {
-        Bundle extra = getIntent().getExtras();
-        RectF cropRect = mCropView.getCropRectangle();
-        if (cropRect == null) return;
-        mState = STATE_SAVING;
-        int messageId = extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER)
-                ? R.string.wallpaper
-                : R.string.saving_image;
-        mProgressDialog = ProgressDialog.show(
-                this, null, getString(messageId), true, false);
-        mSaveTask = getThreadPool().submit(new SaveOutput(cropRect),
-                new FutureListener<Intent>() {
-            @Override
-            public void onFutureDone(Future<Intent> future) {
-                mSaveTask = null;
-                if (future.isCancelled()) return;
-                Intent intent = future.get();
-                if (intent != null) {
-                    mMainHandler.sendMessage(mMainHandler.obtainMessage(
-                            MSG_SAVE_COMPLETE, intent));
-                } else {
-                    mMainHandler.sendEmptyMessage(MSG_SHOW_SAVE_ERROR);
-                }
-            }
-        });
-    }
-
-    private Bitmap getCroppedImage(Rect rect) {
-        Utils.assertTrue(rect.width() > 0 && rect.height() > 0);
-
-        Bundle extras = getIntent().getExtras();
-        // (outputX, outputY) = the width and height of the returning bitmap.
-        int outputX = rect.width();
-        int outputY = rect.height();
-        if (extras != null) {
-            outputX = extras.getInt(KEY_OUTPUT_X, outputX);
-            outputY = extras.getInt(KEY_OUTPUT_Y, outputY);
-        }
-
-        if (outputX * outputY > MAX_PIXEL_COUNT) {
-            float scale = FloatMath.sqrt((float) MAX_PIXEL_COUNT / outputX / outputY);
-            Log.w(TAG, "scale down the cropped image: " + scale);
-            outputX = Math.round(scale * outputX);
-            outputY = Math.round(scale * outputY);
-        }
-
-        // (rect.width() * scaleX, rect.height() * scaleY) =
-        // the size of drawing area in output bitmap
-        float scaleX = 1;
-        float scaleY = 1;
-        Rect dest = new Rect(0, 0, outputX, outputY);
-        if (extras == null || extras.getBoolean(KEY_SCALE, true)) {
-            scaleX = (float) outputX / rect.width();
-            scaleY = (float) outputY / rect.height();
-            if (extras == null || !extras.getBoolean(
-                    KEY_SCALE_UP_IF_NEEDED, false)) {
-                if (scaleX > 1f) scaleX = 1;
-                if (scaleY > 1f) scaleY = 1;
-            }
-        }
-
-        // Keep the content in the center (or crop the content)
-        int rectWidth = Math.round(rect.width() * scaleX);
-        int rectHeight = Math.round(rect.height() * scaleY);
-        dest.set(Math.round((outputX - rectWidth) / 2f),
-                Math.round((outputY - rectHeight) / 2f),
-                Math.round((outputX + rectWidth) / 2f),
-                Math.round((outputY + rectHeight) / 2f));
-
-        if (mBitmapInIntent != null) {
-            Bitmap source = mBitmapInIntent;
-            Bitmap result = Bitmap.createBitmap(
-                    outputX, outputY, Config.ARGB_8888);
-            Canvas canvas = new Canvas(result);
-            canvas.drawBitmap(source, rect, dest, null);
-            return result;
-        }
-
-        if (mUseRegionDecoder) {
-            int rotation = mMediaItem.getFullImageRotation();
-            rotateRectangle(rect, mCropView.getImageWidth(),
-                    mCropView.getImageHeight(), 360 - rotation);
-            rotateRectangle(dest, outputX, outputY, 360 - rotation);
-
-            BitmapFactory.Options options = new BitmapFactory.Options();
-            int sample = BitmapUtils.computeSampleSizeLarger(
-                    Math.max(scaleX, scaleY));
-            options.inSampleSize = sample;
-
-            // The decoding result is what we want if
-            //   1. The size of the decoded bitmap match the destination's size
-            //   2. The destination covers the whole output bitmap
-            //   3. No rotation
-            if ((rect.width() / sample) == dest.width()
-                    && (rect.height() / sample) == dest.height()
-                    && (outputX == dest.width()) && (outputY == dest.height())
-                    && rotation == 0) {
-                // To prevent concurrent access in GLThread
-                synchronized (mRegionDecoder) {
-                    return mRegionDecoder.decodeRegion(rect, options);
-                }
-            }
-            Bitmap result = Bitmap.createBitmap(
-                    outputX, outputY, Config.ARGB_8888);
-            Canvas canvas = new Canvas(result);
-            rotateCanvas(canvas, outputX, outputY, rotation);
-            drawInTiles(canvas, mRegionDecoder, rect, dest, sample);
-            return result;
-        } else {
-            int rotation = mMediaItem.getRotation();
-            rotateRectangle(rect, mCropView.getImageWidth(),
-                    mCropView.getImageHeight(), 360 - rotation);
-            rotateRectangle(dest, outputX, outputY, 360 - rotation);
-            Bitmap result = Bitmap.createBitmap(outputX, outputY, Config.ARGB_8888);
-            Canvas canvas = new Canvas(result);
-            rotateCanvas(canvas, outputX, outputY, rotation);
-            canvas.drawBitmap(mBitmap,
-                    rect, dest, new Paint(Paint.FILTER_BITMAP_FLAG));
-            return result;
-        }
-    }
-
-    private static void rotateCanvas(
-            Canvas canvas, int width, int height, int rotation) {
-        canvas.translate(width / 2, height / 2);
-        canvas.rotate(rotation);
-        if (((rotation / 90) & 0x01) == 0) {
-            canvas.translate(-width / 2, -height / 2);
-        } else {
-            canvas.translate(-height / 2, -width / 2);
-        }
-    }
-
-    private static void rotateRectangle(
-            Rect rect, int width, int height, int rotation) {
-        if (rotation == 0 || rotation == 360) return;
-
-        int w = rect.width();
-        int h = rect.height();
-        switch (rotation) {
-            case 90: {
-                rect.top = rect.left;
-                rect.left = height - rect.bottom;
-                rect.right = rect.left + h;
-                rect.bottom = rect.top + w;
-                return;
-            }
-            case 180: {
-                rect.left = width - rect.right;
-                rect.top = height - rect.bottom;
-                rect.right = rect.left + w;
-                rect.bottom = rect.top + h;
-                return;
-            }
-            case 270: {
-                rect.left = rect.top;
-                rect.top = width - rect.right;
-                rect.right = rect.left + h;
-                rect.bottom = rect.top + w;
-                return;
-            }
-            default: throw new AssertionError();
-        }
-    }
-
-    private void drawInTiles(Canvas canvas,
-            BitmapRegionDecoder decoder, Rect rect, Rect dest, int sample) {
-        int tileSize = TILE_SIZE * sample;
-        Rect tileRect = new Rect();
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inPreferredConfig = Config.ARGB_8888;
-        options.inSampleSize = sample;
-        canvas.translate(dest.left, dest.top);
-        canvas.scale((float) sample * dest.width() / rect.width(),
-                (float) sample * dest.height() / rect.height());
-        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
-        for (int tx = rect.left, x = 0;
-                tx < rect.right; tx += tileSize, x += TILE_SIZE) {
-            for (int ty = rect.top, y = 0;
-                    ty < rect.bottom; ty += tileSize, y += TILE_SIZE) {
-                tileRect.set(tx, ty, tx + tileSize, ty + tileSize);
-                if (tileRect.intersect(rect)) {
-                    Bitmap bitmap;
-
-                    // To prevent concurrent access in GLThread
-                    synchronized (decoder) {
-                        bitmap = decoder.decodeRegion(tileRect, options);
-                    }
-                    canvas.drawBitmap(bitmap, x, y, paint);
-                    bitmap.recycle();
-                }
-            }
-        }
-    }
-
-    private void onBitmapRegionDecoderAvailable(
-            BitmapRegionDecoder regionDecoder) {
-
-        if (regionDecoder == null) {
-            Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show();
-            finish();
-            return;
-        }
-        mRegionDecoder = regionDecoder;
-        mUseRegionDecoder = true;
-        mState = STATE_LOADED;
-
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        int width = regionDecoder.getWidth();
-        int height = regionDecoder.getHeight();
-        options.inSampleSize = BitmapUtils.computeSampleSize(width, height,
-                BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT);
-        mBitmap = regionDecoder.decodeRegion(
-                new Rect(0, 0, width, height), options);
-
-        mBitmapScreenNail = new BitmapScreenNail(mBitmap);
-
-        TileImageViewAdapter adapter = new TileImageViewAdapter();
-        adapter.setScreenNail(mBitmapScreenNail, width, height);
-        adapter.setRegionDecoder(regionDecoder);
-
-        mCropView.setDataModel(adapter, mMediaItem.getFullImageRotation());
-        if (mDoFaceDetection) {
-            mCropView.detectFaces(mBitmap);
-        } else {
-            mCropView.initializeHighlightRectangle();
-        }
-    }
-
-    private void onBitmapAvailable(Bitmap bitmap) {
-        if (bitmap == null) {
-            Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show();
-            finish();
-            return;
-        }
-        mUseRegionDecoder = false;
-        mState = STATE_LOADED;
-
-        mBitmap = bitmap;
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        mCropView.setDataModel(new BitmapTileProvider(bitmap, 512),
-                mMediaItem.getRotation());
-        if (mDoFaceDetection) {
-            mCropView.detectFaces(bitmap);
-        } else {
-            mCropView.initializeHighlightRectangle();
-        }
-    }
-
-    private void setCropParameters() {
-        Bundle extras = getIntent().getExtras();
-        if (extras == null)
-            return;
-        int aspectX = extras.getInt(KEY_ASPECT_X, 0);
-        int aspectY = extras.getInt(KEY_ASPECT_Y, 0);
-        if (aspectX != 0 && aspectY != 0) {
-            mCropView.setAspectRatio((float) aspectX / aspectY);
-        }
-
-        float spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0);
-        float spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0);
-        if (spotlightX != 0 && spotlightY != 0) {
-            mCropView.setSpotlightRatio(spotlightX, spotlightY);
-        }
-    }
-
-    private void initializeData() {
-        Bundle extras = getIntent().getExtras();
-
-        if (extras != null) {
-            if (extras.containsKey(KEY_NO_FACE_DETECTION)) {
-                mDoFaceDetection = !extras.getBoolean(KEY_NO_FACE_DETECTION);
-            }
-
-            mBitmapInIntent = extras.getParcelable(KEY_DATA);
-
-            if (mBitmapInIntent != null) {
-                mBitmapTileProvider =
-                        new BitmapTileProvider(mBitmapInIntent, MAX_BACKUP_IMAGE_SIZE);
-                mCropView.setDataModel(mBitmapTileProvider, 0);
-                if (mDoFaceDetection) {
-                    mCropView.detectFaces(mBitmapInIntent);
-                } else {
-                    mCropView.initializeHighlightRectangle();
-                }
-                mState = STATE_LOADED;
-                return;
-            }
-        }
-
-        mProgressDialog = ProgressDialog.show(
-                this, null, getString(R.string.loading_image), true, true);
-        mProgressDialog.setCanceledOnTouchOutside(false);
-        mProgressDialog.setCancelMessage(mMainHandler.obtainMessage(MSG_CANCEL_DIALOG));
-
-        mMediaItem = getMediaItemFromIntentData();
-        if (mMediaItem == null) return;
-
-        boolean supportedByBitmapRegionDecoder =
-            (mMediaItem.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0;
-        if (supportedByBitmapRegionDecoder) {
-            mLoadTask = getThreadPool().submit(new LoadDataTask(mMediaItem),
-                    new FutureListener<BitmapRegionDecoder>() {
-                @Override
-                public void onFutureDone(Future<BitmapRegionDecoder> future) {
-                    mLoadTask = null;
-                    BitmapRegionDecoder decoder = future.get();
-                    if (future.isCancelled()) {
-                        if (decoder != null) decoder.recycle();
-                        return;
-                    }
-                    mMainHandler.sendMessage(mMainHandler.obtainMessage(
-                            MSG_LARGE_BITMAP, decoder));
-                }
-            });
-        } else {
-            mLoadBitmapTask = getThreadPool().submit(new LoadBitmapDataTask(mMediaItem),
-                    new FutureListener<Bitmap>() {
-                @Override
-                public void onFutureDone(Future<Bitmap> future) {
-                    mLoadBitmapTask = null;
-                    Bitmap bitmap = future.get();
-                    if (future.isCancelled()) {
-                        if (bitmap != null) bitmap.recycle();
-                        return;
-                    }
-                    mMainHandler.sendMessage(mMainHandler.obtainMessage(
-                            MSG_BITMAP, bitmap));
-                }
-            });
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (mState == STATE_INIT) initializeData();
-        if (mState == STATE_SAVING) onSaveClicked();
-
-        // TODO: consider to do it in GLView system
-        GLRoot root = getGLRoot();
-        root.lockRenderThread();
-        try {
-            mCropView.resume();
-        } finally {
-            root.unlockRenderThread();
-        }
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        dismissProgressDialogIfShown();
-
-        Future<BitmapRegionDecoder> loadTask = mLoadTask;
-        if (loadTask != null && !loadTask.isDone()) {
-            // load in progress, try to cancel it
-            loadTask.cancel();
-            loadTask.waitDone();
-        }
-
-        Future<Bitmap> loadBitmapTask = mLoadBitmapTask;
-        if (loadBitmapTask != null && !loadBitmapTask.isDone()) {
-            // load in progress, try to cancel it
-            loadBitmapTask.cancel();
-            loadBitmapTask.waitDone();
-        }
-
-        Future<Intent> saveTask = mSaveTask;
-        if (saveTask != null && !saveTask.isDone()) {
-            // save in progress, try to cancel it
-            saveTask.cancel();
-            saveTask.waitDone();
-        }
-        GLRoot root = getGLRoot();
-        root.lockRenderThread();
-        try {
-            mCropView.pause();
-        } finally {
-            root.unlockRenderThread();
-        }
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (mBitmapScreenNail != null) {
-            mBitmapScreenNail.recycle();
-            mBitmapScreenNail = null;
-        }
-    }
-
-    private void dismissProgressDialogIfShown() {
-        if (mProgressDialog != null) {
-            mProgressDialog.dismiss();
-            mProgressDialog = null;
-        }
-    }
-
-    private MediaItem getMediaItemFromIntentData() {
-        Uri uri = getIntent().getData();
-        DataManager manager = getDataManager();
-        Path path = manager.findPathByUri(uri, getIntent().getType());
-        if (path == null) {
-            Log.w(TAG, "cannot get path for: " + uri + ", or no data given");
-            return null;
-        }
-        return (MediaItem) manager.getMediaObject(path);
-    }
-
-    private class LoadDataTask implements Job<BitmapRegionDecoder> {
-        MediaItem mItem;
-
-        public LoadDataTask(MediaItem item) {
-            mItem = item;
-        }
-
-        @Override
-        public BitmapRegionDecoder run(JobContext jc) {
-            return mItem == null ? null : mItem.requestLargeImage().run(jc);
-        }
-    }
-
-    private class LoadBitmapDataTask implements Job<Bitmap> {
-        MediaItem mItem;
-
-        public LoadBitmapDataTask(MediaItem item) {
-            mItem = item;
-        }
-        @Override
-        public Bitmap run(JobContext jc) {
-            return mItem == null
-                    ? null
-                    : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc);
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/app/FilmstripPage.java b/src/com/android/gallery3d/app/FilmstripPage.java
new file mode 100644
index 0000000..a9726cd
--- /dev/null
+++ b/src/com/android/gallery3d/app/FilmstripPage.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.app;
+
+public class FilmstripPage extends PhotoPage {
+
+}
diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java
index e28404f..baef56b 100644
--- a/src/com/android/gallery3d/app/Gallery.java
+++ b/src/com/android/gallery3d/app/Gallery.java
@@ -17,15 +17,15 @@
 package com.android.gallery3d.app;
 
 import android.app.Dialog;
-import android.content.AsyncQueryHandler;
 import android.content.ContentResolver;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
-import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.OpenableColumns;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.Toast;
@@ -222,7 +222,7 @@
                     }
                 }
 
-                getStateManager().startState(PhotoPage.class, data);
+                getStateManager().startState(SinglePhotoPage.class, data);
             }
         }
     }
@@ -250,4 +250,25 @@
             mVersionCheckDialog = null;
         }
     }
+
+    @Override
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        final boolean isTouchPad = (event.getSource()
+                & InputDevice.SOURCE_CLASS_POSITION) != 0;
+        if (isTouchPad) {
+            float maxX = event.getDevice().getMotionRange(MotionEvent.AXIS_X).getMax();
+            float maxY = event.getDevice().getMotionRange(MotionEvent.AXIS_Y).getMax();
+            View decor = getWindow().getDecorView();
+            float scaleX = decor.getWidth() / maxX;
+            float scaleY = decor.getHeight() / maxY;
+            float x = event.getX() * scaleX;
+            //x = decor.getWidth() - x; // invert x
+            float y = event.getY() * scaleY;
+            //y = decor.getHeight() - y; // invert y
+            MotionEvent touchEvent = MotionEvent.obtain(event.getDownTime(),
+                    event.getEventTime(), event.getAction(), x, y, event.getMetaState());
+            return dispatchTouchEvent(touchEvent);
+        }
+        return super.onGenericMotionEvent(event);
+    }
 }
diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java
index 0fb5e51..588f584 100644
--- a/src/com/android/gallery3d/app/GalleryActionBar.java
+++ b/src/com/android/gallery3d/app/GalleryActionBar.java
@@ -422,7 +422,8 @@
         return mActionBarMenu;
     }
 
-    public void setShareIntents(Intent sharePanoramaIntent, Intent shareIntent) {
+    public void setShareIntents(Intent sharePanoramaIntent, Intent shareIntent,
+        ShareActionProvider.OnShareTargetSelectedListener onShareListener) {
         mSharePanoramaIntent = sharePanoramaIntent;
         if (mSharePanoramaActionProvider != null) {
             mSharePanoramaActionProvider.setShareIntent(sharePanoramaIntent);
@@ -430,6 +431,8 @@
         mShareIntent = shareIntent;
         if (mShareActionProvider != null) {
             mShareActionProvider.setShareIntent(shareIntent);
+            mShareActionProvider.setOnShareTargetSelectedListener(
+                onShareListener);
         }
     }
 }
diff --git a/src/com/android/gallery3d/app/GalleryAppImpl.java b/src/com/android/gallery3d/app/GalleryAppImpl.java
index 561589b..2abdaa0 100644
--- a/src/com/android/gallery3d/app/GalleryAppImpl.java
+++ b/src/com/android/gallery3d/app/GalleryAppImpl.java
@@ -17,12 +17,9 @@
 package com.android.gallery3d.app;
 
 import android.app.Application;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.os.AsyncTask;
 
-import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.DownloadCache;
 import com.android.gallery3d.data.ImageCacheService;
@@ -31,6 +28,8 @@
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.LightCycleHelper;
 import com.android.gallery3d.util.ThreadPool;
+import com.android.gallery3d.util.UsageStatistics;
+import com.android.photos.data.MediaCache;
 
 import java.io.File;
 
@@ -54,6 +53,8 @@
         GalleryUtils.initialize(this);
         WidgetUtils.initialize(this);
         PicasaSource.initialize(this);
+        UsageStatistics.initialize(this);
+        MediaCache.initialize(this);
 
         mStitchingProgressManager = LightCycleHelper.createStitchingManagerInstance(this);
         if (mStitchingProgressManager != null) {
diff --git a/src/com/android/gallery3d/app/ManageCachePage.java b/src/com/android/gallery3d/app/ManageCachePage.java
index 37a9762..4f5c358 100644
--- a/src/com/android/gallery3d/app/ManageCachePage.java
+++ b/src/com/android/gallery3d/app/ManageCachePage.java
@@ -35,8 +35,8 @@
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.ui.CacheStorageUsageInfo;
-import com.android.gallery3d.ui.GLCanvas;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.ManageCacheDrawer;
diff --git a/src/com/android/gallery3d/app/MovieActivity.java b/src/com/android/gallery3d/app/MovieActivity.java
index 3123644..40edbbe 100644
--- a/src/com/android/gallery3d/app/MovieActivity.java
+++ b/src/com/android/gallery3d/app/MovieActivity.java
@@ -127,6 +127,9 @@
     private void initializeActionBar(Intent intent) {
         mUri = intent.getData();
         final ActionBar actionBar = getActionBar();
+        if (actionBar == null) {
+            return;
+        }
         setActionBarLogoFromIntent(intent);
         actionBar.setDisplayOptions(
                 ActionBar.DISPLAY_HOME_AS_UP,
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index 85dc442..513d052 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -28,6 +28,8 @@
 import android.graphics.Color;
 import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.Virtualizer;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
@@ -67,6 +69,7 @@
     private static final String CMDNAME = "command";
     private static final String CMDPAUSE = "pause";
 
+    private static final String VIRTUALIZE_EXTRA = "virtualize";
     private static final long BLACK_TIMEOUT = 500;
 
     // If we resume the acitivty with in RESUMEABLE_TIMEOUT, we will keep playing.
@@ -74,8 +77,8 @@
     private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins
 
     private Context mContext;
-    private final View mRootView;
     private final VideoView mVideoView;
+    private final View mRootView;
     private final Bookmarker mBookmarker;
     private final Uri mUri;
     private final Handler mHandler = new Handler();
@@ -93,6 +96,8 @@
     // If the time bar is visible.
     private boolean mShowing;
 
+    private Virtualizer mVirtualizer;
+
     private final Runnable mPlayingChecker = new Runnable() {
         @Override
         public void run() {
@@ -128,6 +133,31 @@
         mVideoView.setOnErrorListener(this);
         mVideoView.setOnCompletionListener(this);
         mVideoView.setVideoURI(mUri);
+        if (mVirtualizer != null) {
+            mVirtualizer.release();
+            mVirtualizer = null;
+        }
+
+        Intent ai = movieActivity.getIntent();
+        boolean virtualize = ai.getBooleanExtra(VIRTUALIZE_EXTRA, false);
+        if (virtualize) {
+            int session = mVideoView.getAudioSessionId();
+            if (session != 0) {
+                Virtualizer virt = new Virtualizer(0, session);
+                AudioEffect.Descriptor descriptor = virt.getDescriptor();
+                String uuid = descriptor.uuid.toString();
+                if (uuid.equals("36103c52-8514-11e2-9e96-0800200c9a66") ||
+                        uuid.equals("36103c50-8514-11e2-9e96-0800200c9a66")) {
+                    mVirtualizer = virt;
+                    mVirtualizer.setEnabled(true);
+                } else {
+                    // This is not the audio virtualizer we're looking for
+                    virt.release();
+                }
+            } else {
+                Log.w(TAG, "no session");
+            }
+        }
         mVideoView.setOnTouchListener(new View.OnTouchListener() {
             @Override
             public boolean onTouch(View v, MotionEvent event) {
@@ -191,7 +221,6 @@
                 if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
                         && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
                     mController.show();
-                    mRootView.setBackgroundColor(Color.BLACK);
                 }
             }
         });
@@ -271,6 +300,10 @@
     }
 
     public void onDestroy() {
+        if (mVirtualizer != null) {
+            mVirtualizer.release();
+            mVirtualizer = null;
+        }
         mVideoView.stopPlayback();
         mAudioBecomingNoisyReceiver.unregister();
     }
diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java
new file mode 100644
index 0000000..d3f3aa5
--- /dev/null
+++ b/src/com/android/gallery3d/app/MuteVideo.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.app;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.MediaStore;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.SaveVideoFileInfo;
+import com.android.gallery3d.util.SaveVideoFileUtils;
+
+import java.io.IOException;
+
+public class MuteVideo {
+
+    private ProgressDialog mMuteProgress;
+
+    private String mFilePath = null;
+    private Uri mUri = null;
+    private SaveVideoFileInfo mDstFileInfo = null;
+    private Activity mActivity = null;
+    private final Handler mHandler = new Handler();
+
+    final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss";
+
+    public MuteVideo(String filePath, Uri uri, Activity activity) {
+        mUri = uri;
+        mFilePath = filePath;
+        mActivity = activity;
+    }
+
+    public void muteInBackground() {
+        mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME,
+                mActivity.getContentResolver(), mUri,
+                mActivity.getString(R.string.folder_download));
+
+        showProgressDialog();
+        new Thread(new Runnable() {
+                @Override
+            public void run() {
+                try {
+                    VideoUtils.startMute(mFilePath, mDstFileInfo);
+                    SaveVideoFileUtils.insertContent(
+                            mDstFileInfo, mActivity.getContentResolver(), mUri);
+                } catch (IOException e) {
+                    Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err),
+                            Toast.LENGTH_SHORT).show();
+                }
+                // After muting is done, trigger the UI changed.
+                mHandler.post(new Runnable() {
+                        @Override
+                    public void run() {
+                        Toast.makeText(mActivity.getApplicationContext(),
+                                mActivity.getString(R.string.save_into,
+                                        mDstFileInfo.mFolderName),
+                                Toast.LENGTH_SHORT)
+                                .show();
+
+                        if (mMuteProgress != null) {
+                            mMuteProgress.dismiss();
+                            mMuteProgress = null;
+
+                            // Show the result only when the activity not
+                            // stopped.
+                            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
+                            intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*");
+                            intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
+                            mActivity.startActivity(intent);
+                        }
+                    }
+                });
+            }
+        }).start();
+    }
+
+    private void showProgressDialog() {
+        mMuteProgress = new ProgressDialog(mActivity);
+        mMuteProgress.setTitle(mActivity.getString(R.string.muting));
+        mMuteProgress.setMessage(mActivity.getString(R.string.please_wait));
+        mMuteProgress.setCancelable(false);
+        mMuteProgress.setCanceledOnTouchOutside(false);
+        mMuteProgress.show();
+    }
+}
diff --git a/src/com/android/gallery3d/app/NotificationIds.java b/src/com/android/gallery3d/app/NotificationIds.java
new file mode 100644
index 0000000..d697d85
--- /dev/null
+++ b/src/com/android/gallery3d/app/NotificationIds.java
@@ -0,0 +1,22 @@
+/*
+ * 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.gallery3d.app;
+
+public class NotificationIds {
+    public static final int INGEST_NOTIFICATION_SCANNING = 10;
+    public static final int INGEST_NOTIFICATION_IMPORTING = 11;
+}
diff --git a/src/com/android/gallery3d/app/OrientationManager.java b/src/com/android/gallery3d/app/OrientationManager.java
index 0a644ef..f2f632c 100644
--- a/src/com/android/gallery3d/app/OrientationManager.java
+++ b/src/com/android/gallery3d/app/OrientationManager.java
@@ -25,6 +25,7 @@
 import android.view.OrientationEventListener;
 import android.view.Surface;
 
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.ui.OrientationSource;
 
 public class OrientationManager implements OrientationSource {
@@ -71,7 +72,11 @@
     public void lockOrientation() {
         if (mOrientationLocked) return;
         mOrientationLocked = true;
-        mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
+        if (ApiHelper.HAS_ORIENTATION_LOCK) {
+            mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED);
+        } else {
+            mActivity.setRequestedOrientation(calculateCurrentScreenOrientation());
+        }
     }
 
     // Unlock the framework orientation, so it can change when the device
@@ -80,7 +85,7 @@
         if (!mOrientationLocked) return;
         mOrientationLocked = false;
         Log.d(TAG, "unlock orientation");
-        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+        mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
     }
 
     private int calculateCurrentScreenOrientation() {
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index 8485b25..fd3a7cf 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -23,19 +23,18 @@
 
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.ContentListener;
 import com.android.gallery3d.data.LocalMediaItem;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.TiledTexture;
 import com.android.gallery3d.ui.PhotoView;
 import com.android.gallery3d.ui.ScreenNail;
 import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.ui.TileImageViewAdapter;
 import com.android.gallery3d.ui.TiledScreenNail;
-import com.android.gallery3d.ui.TiledTexture;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 import com.android.gallery3d.util.MediaSetUtils;
@@ -550,9 +549,8 @@
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize,
-            int borderSize, BitmapPool pool) {
-        return mTileProvider.getTile(level, x, y, tileSize, borderSize, pool);
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
+        return mTileProvider.getTile(level, x, y, tileSize);
     }
 
     @Override
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 506d1ca..7a71e91 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.nfc.NfcAdapter;
@@ -35,6 +36,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.widget.RelativeLayout;
+import android.widget.ShareActionProvider;
 import android.widget.Toast;
 
 import com.android.camera.CameraActivity;
@@ -51,7 +53,6 @@
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
 import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.MtpSource;
 import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.SecureAlbum;
 import com.android.gallery3d.data.SecureSource;
@@ -59,20 +60,21 @@
 import com.android.gallery3d.data.SnailItem;
 import com.android.gallery3d.data.SnailSource;
 import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropActivity;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.ui.DetailsHelper;
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
 import com.android.gallery3d.ui.GLView;
-import com.android.gallery3d.ui.ImportCompleteListener;
 import com.android.gallery3d.ui.MenuExecutor;
 import com.android.gallery3d.ui.PhotoView;
 import com.android.gallery3d.ui.SelectionManager;
 import com.android.gallery3d.ui.SynchronizedHandler;
 import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.UsageStatistics;
 
-public class PhotoPage extends ActivityState implements
-        PhotoView.Listener, AppBridge.Server,
+public abstract class PhotoPage extends ActivityState implements
+        PhotoView.Listener, AppBridge.Server, ShareActionProvider.OnShareTargetSelectedListener,
         PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
     private static final String TAG = "PhotoPage";
 
@@ -119,6 +121,7 @@
     public static final int MSG_ALBUMPAGE_PICKED = 4;
 
     public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
+    public static final String ACTION_SIMPLE_EDIT = "action_simple_edit";
 
     private GalleryApp mApplication;
     private SelectionManager mSelectionManager;
@@ -345,7 +348,7 @@
                         }
 
                         if (stayedOnCamera) {
-                            if (mAppBridge == null) {
+                            if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) {
                                 launchCamera();
                                 /* We got here by swiping from photo 1 to the
                                    placeholder, so make it be the thing that
@@ -390,7 +393,7 @@
                             }
                             Intent shareIntent = createShareIntent(mCurrentPhoto);
 
-                            mActionBar.setShareIntents(panoramaIntent, shareIntent);
+                            mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
                             setNfcBeamPushUri(contentUri);
                         }
                         break;
@@ -514,6 +517,10 @@
                         if (oldIndex == 0 && mCurrentIndex > 0
                                 && !mPhotoView.getFilmMode()) {
                             mPhotoView.setFilmMode(true);
+                            if (mAppBridge != null) {
+                                UsageStatistics.onEvent("CameraToFilmstrip",
+                                        UsageStatistics.TRANSITION_SWIPE, null);
+                            }
                         } else if (oldIndex == 2 && mCurrentIndex == 1) {
                             mCameraSwitchCutoff = SystemClock.uptimeMillis() +
                                     CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
@@ -671,7 +678,7 @@
     }
 
     private void overrideTransitionToEditor() {
-        ((Activity) mActivity).overridePendingTransition(android.R.anim.slide_in_left,
+        ((Activity) mActivity).overridePendingTransition(android.R.anim.fade_in,
                 android.R.anim.fade_out);
     }
 
@@ -717,6 +724,28 @@
         overrideTransitionToEditor();
     }
 
+    private void launchSimpleEditor() {
+        MediaItem current = mModel.getMediaItem(0);
+        if (current == null || (current.getSupportedOperations()
+                & MediaObject.SUPPORT_EDIT) == 0) {
+            return;
+        }
+
+        Intent intent = new Intent(ACTION_SIMPLE_EDIT);
+
+        intent.setDataAndType(current.getContentUri(), current.getMimeType())
+                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        if (mActivity.getPackageManager()
+                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
+            intent.setAction(Intent.ACTION_EDIT);
+        }
+        intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
+                mActivity.isFullscreen());
+        ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
+                REQUEST_EDIT);
+        overrideTransitionToEditor();
+    }
+
     private void requestDeferredUpdate() {
         mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
         if (!mDeferredUpdateWaiting) {
@@ -786,11 +815,13 @@
         int supportedOperations = mCurrentPhoto.getSupportedOperations();
         if (mSecureAlbum != null) {
             supportedOperations &= MediaObject.SUPPORT_DELETE;
-        } else if (!mHaveImageEditor) {
-            supportedOperations &= ~MediaObject.SUPPORT_EDIT;
+        } else {
+            mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
+            if (!mHaveImageEditor) {
+                supportedOperations &= ~MediaObject.SUPPORT_EDIT;
+            }
         }
         MenuExecutor.updateMenuOperation(menu, supportedOperations);
-        mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
     }
 
     private boolean canDoSlideShow() {
@@ -800,9 +831,6 @@
         if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
             return false;
         }
-        if (MtpSource.isMtpPath(mOriginalSetPathString)) {
-            return false;
-        }
         return true;
     }
 
@@ -844,6 +872,11 @@
         // No bars if it's not allowed.
         if (!mActionBarAllowed) return false;
 
+        Configuration config = mActivity.getResources().getConfiguration();
+        if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
+            return false;
+        }
+
         return true;
     }
 
@@ -1019,11 +1052,16 @@
         refreshHidingMessage();
         MediaItem current = mModel.getMediaItem(0);
 
+        // This is a shield for monkey when it clicks the action bar
+        // menu when transitioning from filmstrip to camera
+        if (current instanceof SnailItem) return true;
+        // TODO: We should check the current photo against the MediaItem
+        // that the menu was initially created for. We need to fix this
+        // after PhotoPage being refactored.
         if (current == null) {
             // item is not ready, ignore
             return true;
         }
-
         int currentIndex = mModel.getCurrentIndex();
         Path path = current.getPath();
 
@@ -1047,8 +1085,8 @@
             }
             case R.id.action_crop: {
                 Activity activity = mActivity;
-                Intent intent = new Intent(FilterShowActivity.CROP_ACTION);
-                intent.setClass(activity, FilterShowActivity.class);
+                Intent intent = new Intent(CropActivity.CROP_ACTION);
+                intent.setClass(activity, CropActivity.class);
                 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
                     .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
@@ -1064,10 +1102,20 @@
                 mActivity.startActivityForResult(intent, REQUEST_TRIM);
                 return true;
             }
+            case R.id.action_mute: {
+                MuteVideo muteVideo = new MuteVideo(current.getFilePath(),
+                        manager.getContentUri(path), mActivity);
+                muteVideo.muteInBackground();
+                return true;
+            }
             case R.id.action_edit: {
                 launchPhotoEditor();
                 return true;
             }
+            case R.id.action_simple_edit: {
+                launchSimpleEditor();
+                return true;
+            }
             case R.id.action_details: {
                 if (mShowDetails) {
                     hideDetails();
@@ -1087,12 +1135,6 @@
                 mSelectionManager.toggle(path);
                 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
                 return true;
-            case R.id.action_import:
-                mSelectionManager.deSelectAll();
-                mSelectionManager.toggle(path);
-                mMenuExecutor.onMenuClicked(item, confirmMsg,
-                        new ImportCompleteListener(mActivity));
-                return true;
             default :
                 return false;
         }
@@ -1211,9 +1253,7 @@
     @Override
     public void onCommitDeleteImage() {
         if (mDeletePath == null) return;
-        mSelectionManager.deSelectAll();
-        mSelectionManager.toggle(mDeletePath);
-        mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
+        mMenuExecutor.startSingleItemAction(R.id.action_delete, mDeletePath);
         mDeletePath = null;
     }
 
@@ -1242,7 +1282,7 @@
                 Bundle data = new Bundle(getData());
                 data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
                 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
-                mActivity.getStateManager().startState(PhotoPage.class, data);
+                mActivity.getStateManager().startState(SinglePhotoPage.class, data);
                 return;
             }
             mModel.setCurrentPhoto(path, mCurrentIndex);
@@ -1334,8 +1374,17 @@
         }
         if (enabled) {
             mHandler.removeMessages(MSG_HIDE_BARS);
+            UsageStatistics.onContentViewChanged(
+                    UsageStatistics.COMPONENT_GALLERY, "FilmstripPage");
         } else {
             refreshHidingMessage();
+            if (mAppBridge == null || mCurrentIndex > 0) {
+                UsageStatistics.onContentViewChanged(
+                        UsageStatistics.COMPONENT_GALLERY, "SinglePhotoPage");
+            } else {
+                UsageStatistics.onContentViewChanged(
+                        UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO
+            }
         }
     }
 
@@ -1495,4 +1544,28 @@
     public void onUndoBarVisibilityChanged(boolean visible) {
         refreshBottomControlsWhenReady();
     }
+
+    @Override
+    public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
+        final long timestampMillis = mCurrentPhoto.getDateInMs();
+        final String mediaType = getMediaTypeString(mCurrentPhoto);
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY,
+                UsageStatistics.ACTION_SHARE,
+                mediaType,
+                        timestampMillis > 0
+                        ? System.currentTimeMillis() - timestampMillis
+                        : -1);
+        return false;
+    }
+
+    private static String getMediaTypeString(MediaItem item) {
+        if (item.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO) {
+            return "Video";
+        } else if (item.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) {
+            return "Photo";
+        } else {
+            return "Unknown:" + item.getMediaType();
+        }
+    }
+
 }
diff --git a/src/com/android/gallery3d/app/SinglePhotoPage.java b/src/com/android/gallery3d/app/SinglePhotoPage.java
new file mode 100644
index 0000000..beb87d3
--- /dev/null
+++ b/src/com/android/gallery3d/app/SinglePhotoPage.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.app;
+
+public class SinglePhotoPage extends PhotoPage {
+
+}
diff --git a/src/com/android/gallery3d/app/SlideshowPage.java b/src/com/android/gallery3d/app/SlideshowPage.java
index 80edb36..174058d 100644
--- a/src/com/android/gallery3d/app/SlideshowPage.java
+++ b/src/com/android/gallery3d/app/SlideshowPage.java
@@ -31,7 +31,7 @@
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
-import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.SlideshowView;
 import com.android.gallery3d.ui.SynchronizedHandler;
@@ -112,11 +112,10 @@
     @Override
     public void onCreate(Bundle data, Bundle restoreState) {
         super.onCreate(data, restoreState);
-        mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR
-                | FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | FLAG_SHOW_WHEN_LOCKED);
+        mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR);
         if (data.getBoolean(KEY_DREAM)) {
             // Dream screensaver only keeps screen on for plugged devices.
-            mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED;
+            mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED | FLAG_SHOW_WHEN_LOCKED;
         } else {
             // User-initiated slideshow would always keep screen on.
             mFlags |= FLAG_SCREEN_ON_ALWAYS;
diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java
index d77279f..c0c84c9 100644
--- a/src/com/android/gallery3d/app/StateManager.java
+++ b/src/com/android/gallery3d/app/StateManager.java
@@ -24,8 +24,10 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
+import com.android.camera.CameraActivity;
 import com.android.gallery3d.anim.StateTransitionAnimation;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.UsageStatistics;
 
 import java.util.Stack;
 
@@ -62,6 +64,14 @@
                     StateTransitionAnimation.Transition.Incoming);
             if (mIsResumed) top.onPause();
         }
+        // Ignore the filmstrip used for the root of the camera app
+        boolean ignoreHit = (mActivity instanceof CameraActivity)
+                && mStack.isEmpty();
+        if (!ignoreHit) {
+            UsageStatistics.onContentViewChanged(
+                    UsageStatistics.COMPONENT_GALLERY,
+                    klass.getSimpleName());
+        }
         state.initialize(mActivity, data);
 
         mStack.push(new StateEntry(data, state));
@@ -91,7 +101,8 @@
         } else {
             mResult = state.mResult;
         }
-
+        UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                klass.getSimpleName());
         mStack.push(new StateEntry(data, state));
         state.onCreate(data, null);
         if (mIsResumed) state.resume();
@@ -210,6 +221,10 @@
         state.onDestroy();
 
         if (top != null && mIsResumed) top.resume();
+        if (top != null) {
+            UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                    top.getClass().getSimpleName());
+        }
     }
 
     public void switchState(ActivityState oldState,
@@ -241,6 +256,8 @@
         mStack.push(new StateEntry(data, state));
         state.onCreate(data, null);
         if (mIsResumed) state.resume();
+        UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                klass.getSimpleName());
     }
 
     public void destroy() {
@@ -255,6 +272,7 @@
     public void restoreFromState(Bundle inState) {
         Log.v(TAG, "restoreFromState");
         Parcelable list[] = inState.getParcelableArray(KEY_MAIN);
+        ActivityState topState = null;
         for (Parcelable parcelable : list) {
             Bundle bundle = (Bundle) parcelable;
             Class<? extends ActivityState> klass =
@@ -273,6 +291,11 @@
             activityState.initialize(mActivity, data);
             activityState.onCreate(data, state);
             mStack.push(new StateEntry(data, activityState));
+            topState = activityState;
+        }
+        if (topState != null) {
+            UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY,
+                    topState.getClass().getSimpleName());
         }
     }
 
diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java
index 9127ad1..cae0166 100644
--- a/src/com/android/gallery3d/app/TrimControllerOverlay.java
+++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java
@@ -23,6 +23,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 
+import com.android.gallery3d.common.ApiHelper;
+
 /**
  * The controller for the Trimming Video.
  */
@@ -41,36 +43,41 @@
         if (mState == State.PLAYING) {
             mPlayPauseReplayView.setVisibility(View.INVISIBLE);
         }
-        mPlayPauseReplayView.setAlpha(1f);
+        if (ApiHelper.HAS_OBJECT_ANIMATION) {
+            mPlayPauseReplayView.setAlpha(1f);
+        }
     }
 
     @Override
     public void showPlaying() {
         super.showPlaying();
+        if (ApiHelper.HAS_OBJECT_ANIMATION) {
+            // Add animation to hide the play button while playing.
+            ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f);
+            anim.setDuration(200);
+            anim.start();
+            anim.addListener(new AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                }
 
-        // Add animation to hide the play button while playing.
-        ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f);
-        anim.setDuration(200);
-        anim.start();
-        anim.addListener(new AnimatorListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-            }
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    hidePlayButtonIfPlaying();
+                }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                hidePlayButtonIfPlaying();
-            }
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    hidePlayButtonIfPlaying();
+                }
 
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                hidePlayButtonIfPlaying();
-            }
-
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-            }
-          });
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+                }
+            });
+        } else {
+            hidePlayButtonIfPlaying();
+        }
     }
 
     @Override
diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java
index 4325a41..1e77281 100644
--- a/src/com/android/gallery3d/app/TrimVideo.java
+++ b/src/com/android/gallery3d/app/TrimVideo.java
@@ -19,19 +19,13 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
-import android.database.Cursor;
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.Handler;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Video;
-import android.provider.MediaStore.Video.VideoColumns;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -40,12 +34,11 @@
 import android.widget.VideoView;
 
 import com.android.gallery3d.R;
-import com.android.gallery3d.util.BucketNames;
+import com.android.gallery3d.util.SaveVideoFileInfo;
+import com.android.gallery3d.util.SaveVideoFileUtils;
 
 import java.io.File;
 import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
 
 public class TrimVideo extends Activity implements
         MediaPlayer.OnErrorListener,
@@ -53,6 +46,7 @@
         ControllerOverlay.Listener {
 
     private VideoView mVideoView;
+    private TextView mSaveVideoTextView;
     private TrimControllerOverlay mController;
     private Context mContext;
     private Uri mUri;
@@ -70,13 +64,8 @@
     private boolean mHasPaused = false;
 
     private String mSrcVideoPath = null;
-    private String mSaveFileName = null;
     private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss";
-    private File mSrcFile = null;
-    private File mDstFile = null;
-    private File mSaveDirectory = null;
-    // For showing the result.
-    private String saveFolderName = null;
+    private SaveVideoFileInfo mDstFileInfo = null;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -93,13 +82,14 @@
         actionBar.setDisplayOptions(displayOptions, displayOptions);
         actionBar.setCustomView(R.layout.trim_menu);
 
-        TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
+        mSaveVideoTextView = (TextView) findViewById(R.id.start_trim);
         mSaveVideoTextView.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View arg0) {
                 trimVideo();
             }
         });
+        mSaveVideoTextView.setEnabled(false);
 
         Intent intent = getIntent();
         mUri = intent.getData();
@@ -221,71 +211,24 @@
         mController.showPaused();
     }
 
-    // Copy from SaveCopyTask.java in terms of how to handle the destination
-    // path and filename : querySource() and getSaveDirectory().
-    private interface ContentResolverQueryCallback {
-        void onCursorResult(Cursor cursor);
-    }
 
-    private void querySource(String[] projection, ContentResolverQueryCallback callback) {
-        ContentResolver contentResolver = getContentResolver();
-        Cursor cursor = null;
-        try {
-            cursor = contentResolver.query(mUri, projection, null, null, null);
-            if ((cursor != null) && cursor.moveToNext()) {
-                callback.onCursorResult(cursor);
-            }
-        } catch (Exception e) {
-            // Ignore error for lacking the data column from the source.
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
+    private boolean isModified() {
+        int delta = mTrimEndTime - mTrimStartTime;
+
+        // Considering that we only trim at sync frame, we don't want to trim
+        // when the time interval is too short or too close to the origin.
+        if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) {
+            return false;
+        } else {
+            return true;
         }
     }
 
-    private File getSaveDirectory() {
-        final File[] dir = new File[1];
-        querySource(new String[] {
-                VideoColumns.DATA }, new ContentResolverQueryCallback() {
-
-            @Override
-            public void onCursorResult(Cursor cursor) {
-                dir[0] = new File(cursor.getString(0)).getParentFile();
-            }
-        });
-        return dir[0];
-    }
-
     private void trimVideo() {
-        int delta = mTrimEndTime - mTrimStartTime;
-        // Considering that we only trim at sync frame, we don't want to trim
-        // when the time interval is too short or too close to the origin.
-        if (delta < 100 ) {
-            Toast.makeText(getApplicationContext(),
-                getString(R.string.trim_too_short),
-                Toast.LENGTH_SHORT).show();
-            return;
-        }
-        if (Math.abs(mVideoView.getDuration() - delta) < 100) {
-            // If no change has been made, go back
-            onBackPressed();
-            return;
-        }
-        // Use the default save directory if the source directory cannot be
-        // saved.
-        mSaveDirectory = getSaveDirectory();
-        if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) {
-            mSaveDirectory = new File(Environment.getExternalStorageDirectory(),
-                    BucketNames.DOWNLOAD);
-            saveFolderName = getString(R.string.folder_download);
-        } else {
-            saveFolderName = mSaveDirectory.getName();
-        }
-        mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date());
 
-        mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4");
-        mSrcFile = new File(mSrcVideoPath);
+        mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME,
+                getContentResolver(), mUri, getString(R.string.folder_download));
+        final File mSrcFile = new File(mSrcVideoPath);
 
         showProgressDialog();
 
@@ -293,9 +236,11 @@
             @Override
             public void run() {
                 try {
-                    TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime);
+                    VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile,
+                            mTrimStartTime, mTrimEndTime);
                     // Update the database for adding a new video file.
-                    insertContent(mDstFile);
+                    SaveVideoFileUtils.insertContent(mDstFileInfo,
+                            getContentResolver(), mUri);
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
@@ -304,7 +249,7 @@
                     @Override
                     public void run() {
                         Toast.makeText(getApplicationContext(),
-                            getString(R.string.save_into) + " " + saveFolderName,
+                            getString(R.string.save_into, mDstFileInfo.mFolderName),
                             Toast.LENGTH_SHORT)
                             .show();
                         // TODO: change trimming into a service to avoid
@@ -314,7 +259,7 @@
                             mProgress = null;
                             // Show the result only when the activity not stopped.
                             Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
-                            intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*");
+                            intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*");
                             intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false);
                             startActivity(intent);
                             finish();
@@ -337,53 +282,6 @@
         mProgress.show();
     }
 
-    /**
-     * Insert the content (saved file) with proper video properties.
-     */
-    private Uri insertContent(File file) {
-        long nowInMs = System.currentTimeMillis();
-        long nowInSec = nowInMs / 1000;
-        final ContentValues values = new ContentValues(12);
-        values.put(Video.Media.TITLE, mSaveFileName);
-        values.put(Video.Media.DISPLAY_NAME, file.getName());
-        values.put(Video.Media.MIME_TYPE, "video/mp4");
-        values.put(Video.Media.DATE_TAKEN, nowInMs);
-        values.put(Video.Media.DATE_MODIFIED, nowInSec);
-        values.put(Video.Media.DATE_ADDED, nowInSec);
-        values.put(Video.Media.DATA, file.getAbsolutePath());
-        values.put(Video.Media.SIZE, file.length());
-        // Copy the data taken and location info from src.
-        String[] projection = new String[] {
-                VideoColumns.DATE_TAKEN,
-                VideoColumns.LATITUDE,
-                VideoColumns.LONGITUDE,
-                VideoColumns.RESOLUTION,
-        };
-
-        // Copy some info from the source file.
-        querySource(projection, new ContentResolverQueryCallback() {
-            @Override
-            public void onCursorResult(Cursor cursor) {
-                long timeTaken = cursor.getLong(0);
-                if (timeTaken > 0) {
-                    values.put(Video.Media.DATE_TAKEN, timeTaken);
-                }
-                double latitude = cursor.getDouble(1);
-                double longitude = cursor.getDouble(2);
-                // TODO: Change || to && after the default location issue is
-                // fixed.
-                if ((latitude != 0f) || (longitude != 0f)) {
-                    values.put(Video.Media.LATITUDE, latitude);
-                    values.put(Video.Media.LONGITUDE, longitude);
-                }
-                values.put(Video.Media.RESOLUTION, cursor.getString(3));
-
-            }
-        });
-
-        return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values);
-    }
-
     @Override
     public void onPlayPause() {
         if (mVideoView.isPlaying()) {
@@ -409,6 +307,8 @@
         mTrimStartTime = start;
         mTrimEndTime = end;
         setProgress();
+        // Enable save if there's modifications
+        mSaveVideoTextView.setEnabled(isModified());
     }
 
     @Override
diff --git a/src/com/android/gallery3d/app/TrimVideoUtils.java b/src/com/android/gallery3d/app/TrimVideoUtils.java
deleted file mode 100644
index ae9b1e9..0000000
--- a/src/com/android/gallery3d/app/TrimVideoUtils.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-// Modified example based on mp4parser google code open source project.
-// http://code.google.com/p/mp4parser/source/browse/trunk/examples/src/main/java/com/googlecode/mp4parser/ShortenExample.java
-
-package com.android.gallery3d.app;
-
-import com.coremedia.iso.IsoFile;
-import com.coremedia.iso.boxes.TimeToSampleBox;
-import com.googlecode.mp4parser.authoring.Movie;
-import com.googlecode.mp4parser.authoring.Track;
-import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
-import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
-import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Shortens/Crops a track
- */
-public class TrimVideoUtils {
-
-    public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
-        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
-        Movie movie = MovieCreator.build(randomAccessFile.getChannel());
-
-        // remove all tracks we will create new tracks from the old
-        List<Track> tracks = movie.getTracks();
-        movie.setTracks(new LinkedList<Track>());
-
-        double startTime = startMs/1000;
-        double endTime = endMs/1000;
-
-        boolean timeCorrected = false;
-
-        // Here we try to find a track that has sync samples. Since we can only start decoding
-        // at such a sample we SHOULD make sure that the start of the new fragment is exactly
-        // such a frame
-        for (Track track : tracks) {
-            if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
-                if (timeCorrected) {
-                    // This exception here could be a false positive in case we have multiple tracks
-                    // with sync samples at exactly the same positions. E.g. a single movie containing
-                    // multiple qualities of the same video (Microsoft Smooth Streaming file)
-
-                    throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported.");
-                }
-                startTime = correctTimeToSyncSample(track, startTime, false);
-                endTime = correctTimeToSyncSample(track, endTime, true);
-                timeCorrected = true;
-            }
-        }
-
-        for (Track track : tracks) {
-            long currentSample = 0;
-            double currentTime = 0;
-            long startSample = -1;
-            long endSample = -1;
-
-            for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
-                TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
-                for (int j = 0; j < entry.getCount(); j++) {
-                    // entry.getDelta() is the amount of time the current sample covers.
-
-                    if (currentTime <= startTime) {
-                        // current sample is still before the new starttime
-                        startSample = currentSample;
-                    }
-                    if (currentTime <= endTime) {
-                        // current sample is after the new start time and still before the new endtime
-                        endSample = currentSample;
-                    } else {
-                        // current sample is after the end of the cropped video
-                        break;
-                    }
-                    currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
-                    currentSample++;
-                }
-            }
-            movie.addTrack(new CroppedTrack(track, startSample, endSample));
-        }
-        IsoFile out = new DefaultMp4Builder().build(movie);
-
-        if (!dst.exists()) {
-            dst.createNewFile();
-        }
-
-        FileOutputStream fos = new FileOutputStream(dst);
-        FileChannel fc = fos.getChannel();
-        out.getBox(fc);  // This one build up the memory.
-
-        fc.close();
-        fos.close();
-        randomAccessFile.close();
-    }
-
-    protected static long getDuration(Track track) {
-        long duration = 0;
-        for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
-            duration += entry.getCount() * entry.getDelta();
-        }
-        return duration;
-    }
-
-    private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
-        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
-        long currentSample = 0;
-        double currentTime = 0;
-        for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
-            TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
-            for (int j = 0; j < entry.getCount(); j++) {
-                if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
-                    // samples always start with 1 but we start with zero therefore +1
-                    timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime;
-                }
-                currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale();
-                currentSample++;
-            }
-        }
-        double previous = 0;
-        for (double timeOfSyncSample : timeOfSyncSamples) {
-            if (timeOfSyncSample > cutHere) {
-                if (next) {
-                    return timeOfSyncSample;
-                } else {
-                    return previous;
-                }
-            }
-            previous = timeOfSyncSample;
-        }
-        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
-    }
-
-
-}
diff --git a/src/com/android/gallery3d/app/UsbDeviceActivity.java b/src/com/android/gallery3d/app/UsbDeviceActivity.java
deleted file mode 100644
index 28bd667..0000000
--- a/src/com/android/gallery3d/app/UsbDeviceActivity.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.app;
-
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-/* This Activity does nothing but receive USB_DEVICE_ATTACHED events from the
- * USB service and springboards to the main Gallery activity
- */
-public final class UsbDeviceActivity extends Activity {
-
-    static final String TAG = "UsbDeviceActivity";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        //
-        Intent intent = new Intent(this, Gallery.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        try {
-            startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.e(TAG, "unable to start Gallery activity", e);
-        }
-        finish();
-    }
-}
diff --git a/src/com/android/gallery3d/app/VideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java
new file mode 100644
index 0000000..a3c3ef2
--- /dev/null
+++ b/src/com/android/gallery3d/app/VideoUtils.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+// Modified example based on mp4parser google code open source project.
+// http://code.google.com/p/mp4parser/source/browse/trunk/examples/src/main/java/com/googlecode/mp4parser/ShortenExample.java
+
+package com.android.gallery3d.app;
+
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaMuxer;
+import android.util.Log;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.SaveVideoFileInfo;
+import com.coremedia.iso.IsoFile;
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
+import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
+import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+public class VideoUtils {
+    private static final String LOGTAG = "VideoUtils";
+    private static final int DEFAULT_BUFFER_SIZE = 1 * 1024 * 1024;
+
+    /**
+     * Remove the sound track.
+     */
+    public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo)
+            throws IOException {
+        if (ApiHelper.HAS_MEDIA_MUXER) {
+            genVideoUsingMuxer(filePath, dstFileInfo.mFile.getPath(), -1, -1,
+                    false, true);
+        } else {
+            startMuteUsingMp4Parser(filePath, dstFileInfo);
+        }
+    }
+
+    /**
+     * Shortens/Crops tracks
+     */
+    public static void startTrim(File src, File dst, int startMs, int endMs)
+            throws IOException {
+        if (ApiHelper.HAS_MEDIA_MUXER) {
+            genVideoUsingMuxer(src.getPath(), dst.getPath(), startMs, endMs,
+                    true, true);
+        } else {
+            trimUsingMp4Parser(src, dst, startMs, endMs);
+        }
+    }
+
+    private static void startMuteUsingMp4Parser(String filePath,
+            SaveVideoFileInfo dstFileInfo) throws FileNotFoundException, IOException {
+        File dst = dstFileInfo.mFile;
+        File src = new File(filePath);
+        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
+        Movie movie = MovieCreator.build(randomAccessFile.getChannel());
+
+        // remove all tracks we will create new tracks from the old
+        List<Track> tracks = movie.getTracks();
+        movie.setTracks(new LinkedList<Track>());
+
+        for (Track track : tracks) {
+            if (track.getHandler().equals("vide")) {
+                movie.addTrack(track);
+            }
+        }
+        writeMovieIntoFile(dst, movie);
+        randomAccessFile.close();
+    }
+
+    private static void writeMovieIntoFile(File dst, Movie movie)
+            throws IOException {
+        if (!dst.exists()) {
+            dst.createNewFile();
+        }
+
+        IsoFile out = new DefaultMp4Builder().build(movie);
+        FileOutputStream fos = new FileOutputStream(dst);
+        FileChannel fc = fos.getChannel();
+        out.getBox(fc); // This one build up the memory.
+
+        fc.close();
+        fos.close();
+    }
+
+    /**
+     * @param srcPath the path of source video file.
+     * @param dstPath the path of destination video file.
+     * @param startMs starting time in milliseconds for trimming. Set to
+     *            negative if starting from beginning.
+     * @param endMs end time for trimming in milliseconds. Set to negative if
+     *            no trimming at the end.
+     * @param useAudio true if keep the audio track from the source.
+     * @param useVideo true if keep the video track from the source.
+     * @throws IOException
+     */
+    private static void genVideoUsingMuxer(String srcPath, String dstPath,
+            int startMs, int endMs, boolean useAudio, boolean useVideo)
+            throws IOException {
+        // Set up MediaExtractor to read from the source.
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcPath);
+
+        int trackCount = extractor.getTrackCount();
+
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+        // Set up the tracks and retrieve the max buffer size for selected
+        // tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer,
+                Integer>(trackCount);
+        int bufferSize = -1;
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+
+            boolean selectCurrentTrack = false;
+
+            if (mime.startsWith("audio/") && useAudio) {
+                selectCurrentTrack = true;
+            } else if (mime.startsWith("video/") && useVideo) {
+                selectCurrentTrack = true;
+            }
+
+            if (selectCurrentTrack) {
+                extractor.selectTrack(i);
+                int dstIndex = muxer.addTrack(format);
+                indexMap.put(i, dstIndex);
+                if (format.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)) {
+                    int newSize = format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
+                    bufferSize = newSize > bufferSize ? newSize : bufferSize;
+                }
+            }
+        }
+
+        if (bufferSize < 0) {
+            bufferSize = DEFAULT_BUFFER_SIZE;
+        }
+
+        // Set up the orientation and starting time for extractor.
+        MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
+        retrieverSrc.setDataSource(srcPath);
+        String degreesString = retrieverSrc.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+        if (degreesString != null) {
+            int degrees = Integer.parseInt(degreesString);
+            if (degrees >= 0) {
+                muxer.setOrientationHint(degrees);
+            }
+        }
+
+        if (startMs > 0) {
+            extractor.seekTo(startMs * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        }
+
+        // Copy the samples from MediaExtractor to MediaMuxer. We will loop
+        // for copying each sample and stop when we get to the end of the source
+        // file or exceed the end time of the trimming.
+        int offset = 0;
+        int trackIndex = -1;
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        muxer.start();
+        while (true) {
+            bufferInfo.offset = offset;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+            if (bufferInfo.size < 0) {
+                Log.d(LOGTAG, "Saw input EOS.");
+                bufferInfo.size = 0;
+                break;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                if (endMs > 0 && bufferInfo.presentationTimeUs > (endMs * 1000)) {
+                    Log.d(LOGTAG, "The current sample is over the trim end time.");
+                    break;
+                } else {
+                    bufferInfo.flags = extractor.getSampleFlags();
+                    trackIndex = extractor.getSampleTrackIndex();
+
+                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
+                            bufferInfo);
+                    extractor.advance();
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        return;
+    }
+
+    private static void trimUsingMp4Parser(File src, File dst, int startMs, int endMs)
+            throws FileNotFoundException, IOException {
+        RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r");
+        Movie movie = MovieCreator.build(randomAccessFile.getChannel());
+
+        // remove all tracks we will create new tracks from the old
+        List<Track> tracks = movie.getTracks();
+        movie.setTracks(new LinkedList<Track>());
+
+        double startTime = startMs / 1000;
+        double endTime = endMs / 1000;
+
+        boolean timeCorrected = false;
+
+        // Here we try to find a track that has sync samples. Since we can only
+        // start decoding at such a sample we SHOULD make sure that the start of
+        // the new fragment is exactly such a frame.
+        for (Track track : tracks) {
+            if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+                if (timeCorrected) {
+                    // This exception here could be a false positive in case we
+                    // have multiple tracks with sync samples at exactly the
+                    // same positions. E.g. a single movie containing multiple
+                    // qualities of the same video (Microsoft Smooth Streaming
+                    // file)
+                    throw new RuntimeException(
+                            "The startTime has already been corrected by" +
+                            " another track with SyncSample. Not Supported.");
+                }
+                startTime = correctTimeToSyncSample(track, startTime, false);
+                endTime = correctTimeToSyncSample(track, endTime, true);
+                timeCorrected = true;
+            }
+        }
+
+        for (Track track : tracks) {
+            long currentSample = 0;
+            double currentTime = 0;
+            long startSample = -1;
+            long endSample = -1;
+
+            for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
+                TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
+                for (int j = 0; j < entry.getCount(); j++) {
+                    // entry.getDelta() is the amount of time the current sample
+                    // covers.
+
+                    if (currentTime <= startTime) {
+                        // current sample is still before the new starttime
+                        startSample = currentSample;
+                    }
+                    if (currentTime <= endTime) {
+                        // current sample is after the new start time and still
+                        // before the new endtime
+                        endSample = currentSample;
+                    } else {
+                        // current sample is after the end of the cropped video
+                        break;
+                    }
+                    currentTime += (double) entry.getDelta()
+                            / (double) track.getTrackMetaData().getTimescale();
+                    currentSample++;
+                }
+            }
+            movie.addTrack(new CroppedTrack(track, startSample, endSample));
+        }
+        writeMovieIntoFile(dst, movie);
+        randomAccessFile.close();
+    }
+
+    private static double correctTimeToSyncSample(Track track, double cutHere,
+            boolean next) {
+        double[] timeOfSyncSamples = new double[track.getSyncSamples().length];
+        long currentSample = 0;
+        double currentTime = 0;
+        for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) {
+            TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i);
+            for (int j = 0; j < entry.getCount(); j++) {
+                if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) {
+                    // samples always start with 1 but we start with zero
+                    // therefore +1
+                    timeOfSyncSamples[Arrays.binarySearch(
+                            track.getSyncSamples(), currentSample + 1)] = currentTime;
+                }
+                currentTime += (double) entry.getDelta()
+                        / (double) track.getTrackMetaData().getTimescale();
+                currentSample++;
+            }
+        }
+        double previous = 0;
+        for (double timeOfSyncSample : timeOfSyncSamples) {
+            if (timeOfSyncSample > cutHere) {
+                if (next) {
+                    return timeOfSyncSample;
+                } else {
+                    return previous;
+                }
+            }
+            previous = timeOfSyncSample;
+        }
+        return timeOfSyncSamples[timeOfSyncSamples.length - 1];
+    }
+
+}
diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java
index 996d3f0..91bc772 100644
--- a/src/com/android/gallery3d/app/Wallpaper.java
+++ b/src/com/android/gallery3d/app/Wallpaper.java
@@ -26,6 +26,8 @@
 import android.view.Display;
 
 import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
 
 /**
  * Wallpaper picker for the gallery application. This just redirects to the
@@ -98,19 +100,18 @@
                 Point size = getDefaultDisplaySize(new Point());
                 float spotlightX = (float) size.x / width;
                 float spotlightY = (float) size.y / height;
-                Intent request = new Intent(CropImage.ACTION_CROP)
+                Intent request = new Intent(FilterShowActivity.CROP_ACTION)
                         .setDataAndType(mPickedItem, IMAGE_TYPE)
                         .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
-                        .putExtra(CropImage.KEY_OUTPUT_X, width)
-                        .putExtra(CropImage.KEY_OUTPUT_Y, height)
-                        .putExtra(CropImage.KEY_ASPECT_X, width)
-                        .putExtra(CropImage.KEY_ASPECT_Y, height)
-                        .putExtra(CropImage.KEY_SPOTLIGHT_X, spotlightX)
-                        .putExtra(CropImage.KEY_SPOTLIGHT_Y, spotlightY)
-                        .putExtra(CropImage.KEY_SCALE, true)
-                        .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true)
-                        .putExtra(CropImage.KEY_NO_FACE_DETECTION, true)
-                        .putExtra(CropImage.KEY_SET_AS_WALLPAPER, true);
+                        .putExtra(CropExtras.KEY_OUTPUT_X, width)
+                        .putExtra(CropExtras.KEY_OUTPUT_Y, height)
+                        .putExtra(CropExtras.KEY_ASPECT_X, width)
+                        .putExtra(CropExtras.KEY_ASPECT_Y, height)
+                        .putExtra(CropExtras.KEY_SPOTLIGHT_X, spotlightX)
+                        .putExtra(CropExtras.KEY_SPOTLIGHT_Y, spotlightY)
+                        .putExtra(CropExtras.KEY_SCALE, true)
+                        .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true)
+                        .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, true);
                 startActivity(request);
                 finish();
             }
diff --git a/src/com/android/gallery3d/data/BitmapPool.java b/src/com/android/gallery3d/data/BitmapPool.java
deleted file mode 100644
index 5bc6d67..0000000
--- a/src/com/android/gallery3d/data/BitmapPool.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.data;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.common.Utils;
-
-import java.util.ArrayList;
-
-public class BitmapPool {
-    @SuppressWarnings("unused")
-    private static final String TAG = "BitmapPool";
-
-    private final ArrayList<Bitmap> mPool;
-    private final int mPoolLimit;
-
-    // mOneSize is true if the pool can only cache Bitmap with one size.
-    private final boolean mOneSize;
-    private final int mWidth, mHeight;  // only used if mOneSize is true
-
-    // Construct a BitmapPool which caches bitmap with the specified size.
-    public BitmapPool(int width, int height, int poolLimit) {
-        mWidth = width;
-        mHeight = height;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = true;
-    }
-
-    // Construct a BitmapPool which caches bitmap with any size;
-    public BitmapPool(int poolLimit) {
-        mWidth = -1;
-        mHeight = -1;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = false;
-    }
-
-    // Get a Bitmap from the pool.
-    public synchronized Bitmap getBitmap() {
-        Utils.assertTrue(mOneSize);
-        int size = mPool.size();
-        return size > 0 ? mPool.remove(size - 1) : null;
-    }
-
-    // Get a Bitmap from the pool with the specified size.
-    public synchronized Bitmap getBitmap(int width, int height) {
-        Utils.assertTrue(!mOneSize);
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            Bitmap b = mPool.get(i);
-            if (b.getWidth() == width && b.getHeight() == height) {
-                return mPool.remove(i);
-            }
-        }
-        return null;
-    }
-
-    // Put a Bitmap into the pool, if the Bitmap has a proper size. Otherwise
-    // the Bitmap will be recycled. If the pool is full, an old Bitmap will be
-    // recycled.
-    public void recycle(Bitmap bitmap) {
-        if (bitmap == null) return;
-        if (mOneSize && ((bitmap.getWidth() != mWidth) ||
-                (bitmap.getHeight() != mHeight))) {
-            bitmap.recycle();
-            return;
-        }
-        synchronized (this) {
-            if (mPool.size() >= mPoolLimit) mPool.remove(0);
-            mPool.add(bitmap);
-        }
-    }
-
-    public synchronized void clear() {
-        mPool.clear();
-    }
-
-    public boolean isOneSize() {
-        return mOneSize;
-    }
-}
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index 4ec7b6d..38865e9 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -16,13 +16,13 @@
 
 package com.android.gallery3d.data;
 
+import android.content.Context;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
 
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.app.StitchingChangeListener;
-import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
 import com.android.gallery3d.data.MediaSet.ItemConsumer;
@@ -65,16 +65,17 @@
     // to prevent concurrency issue.
     public static final Object LOCK = new Object();
 
+    public static DataManager from(Context context) {
+        GalleryApp app = (GalleryApp) context.getApplicationContext();
+        return app.getDataManager();
+    }
+
     private static final String TAG = "DataManager";
 
     // This is the path for the media set seen by the user at top level.
-    private static final String TOP_SET_PATH = ApiHelper.HAS_MTP
-            ? "/combo/{/mtp,/local/all,/picasa/all}"
-            : "/combo/{/local/all,/picasa/all}";
+    private static final String TOP_SET_PATH = "/combo/{/local/all,/picasa/all}";
 
-    private static final String TOP_IMAGE_SET_PATH = ApiHelper.HAS_MTP
-            ? "/combo/{/mtp,/local/image,/picasa/image}"
-            : "/combo/{/local/image,/picasa/image}";
+    private static final String TOP_IMAGE_SET_PATH = "/combo/{/local/image,/picasa/image}";
 
     private static final String TOP_VIDEO_SET_PATH =
             "/combo/{/local/video,/picasa/video}";
@@ -118,9 +119,6 @@
         // the order matters, the UriSource must come last
         addSource(new LocalSource(mApplication));
         addSource(new PicasaSource(mApplication));
-        if (ApiHelper.HAS_MTP) {
-            addSource(new MtpSource(mApplication));
-        }
         addSource(new ComboSource(mApplication));
         addSource(new ClusterSource(mApplication));
         addSource(new FilterSource(mApplication));
diff --git a/src/com/android/gallery3d/data/DataSourceType.java b/src/com/android/gallery3d/data/DataSourceType.java
index 2761188..ab534d0 100644
--- a/src/com/android/gallery3d/data/DataSourceType.java
+++ b/src/com/android/gallery3d/data/DataSourceType.java
@@ -22,12 +22,10 @@
     public static final int TYPE_NOT_CATEGORIZED = 0;
     public static final int TYPE_LOCAL = 1;
     public static final int TYPE_PICASA = 2;
-    public static final int TYPE_MTP = 3;
-    public static final int TYPE_CAMERA = 4;
+    public static final int TYPE_CAMERA = 3;
 
     private static final Path PICASA_ROOT = Path.fromString("/picasa");
     private static final Path LOCAL_ROOT = Path.fromString("/local");
-    private static final Path MTP_ROOT = Path.fromString("/mtp");
 
     public static int identifySourceType(MediaSet set) {
         if (set == null) {
@@ -40,7 +38,6 @@
         Path prefix = path.getPrefixPath();
 
         if (prefix == PICASA_ROOT) return TYPE_PICASA;
-        if (prefix == MTP_ROOT) return TYPE_MTP;
         if (prefix == LOCAL_ROOT) return TYPE_LOCAL;
 
         return TYPE_NOT_CATEGORIZED;
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java
index 4d3c996..fa70915 100644
--- a/src/com/android/gallery3d/data/DecodeUtils.java
+++ b/src/com/android/gallery3d/data/DecodeUtils.java
@@ -28,6 +28,7 @@
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.ui.Log;
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -246,21 +247,17 @@
     }
 
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc, byte[] data, int offset,
-            int length, BitmapFactory.Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, data, offset, length, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset,
+            int length, BitmapFactory.Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, data, offset, length, options) : null;
+                ? findCachedBitmap(jc, data, offset, length, options) : null;
         try {
             Bitmap bitmap = decode(jc, data, offset, length, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -268,7 +265,7 @@
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, data, offset, length, options);
         }
@@ -277,21 +274,17 @@
     // This is the same as the method above except the source data comes
     // from a file descriptor instead of a byte array.
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc,
-            FileDescriptor fileDescriptor, Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, fileDescriptor, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc,
+            FileDescriptor fileDescriptor, Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, fileDescriptor, options) : null;
+                ? findCachedBitmap(jc, fileDescriptor, options) : null;
         try {
             Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -299,23 +292,21 @@
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, fileDescriptor, options);
         }
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            byte[] data, int offset, int length, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, byte[] data,
+            int offset, int length, Options options) {
         decodeBounds(jc, data, offset, length, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            FileDescriptor fileDescriptor, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor,
+            Options options) {
         decodeBounds(jc, fileDescriptor, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 }
diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java
index ba5862a..950e7de 100644
--- a/src/com/android/gallery3d/data/Exif.java
+++ b/src/com/android/gallery3d/data/Exif.java
@@ -18,144 +18,31 @@
 
 import android.util.Log;
 
+import com.android.gallery3d.exif.ExifInterface;
+
 import java.io.IOException;
 import java.io.InputStream;
 
 public class Exif {
     private static final String TAG = "CameraExif";
 
+    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
     public static int getOrientation(InputStream is) {
         if (is == null) {
             return 0;
         }
-
-        byte[] buf = new byte[8];
-        int length = 0;
-
-        // ISO/IEC 10918-1:1993(E)
-        while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) {
-            int marker = buf[1] & 0xFF;
-
-            // Check if the marker is a padding.
-            if (marker == 0xFF) {
-                continue;
-            }
-
-            // Check if the marker is SOI or TEM.
-            if (marker == 0xD8 || marker == 0x01) {
-                continue;
-            }
-            // Check if the marker is EOI or SOS.
-            if (marker == 0xD9 || marker == 0xDA) {
-                return 0;
-            }
-
-            // Get the length and check if it is reasonable.
-            if (!read(is, buf, 2)) {
-                return 0;
-            }
-            length = pack(buf, 0, 2, false);
-            if (length < 2) {
-                Log.e(TAG, "Invalid length");
-                return 0;
-            }
-            length -= 2;
-
-            // Break if the marker is EXIF in APP1.
-            if (marker == 0xE1 && length >= 6) {
-                if (!read(is, buf, 6)) return 0;
-                length -= 6;
-                if (pack(buf, 0, 4, false) == 0x45786966 &&
-                    pack(buf, 4, 2, false) == 0) {
-                    break;
-                }
-            }
-
-            // Skip other markers.
-            try {
-                is.skip(length);
-            } catch (IOException ex) {
-                return 0;
-            }
-            length = 0;
-        }
-
-        // JEITA CP-3451 Exif Version 2.2
-        if (length > 8) {
-            int offset = 0;
-            byte[] jpeg = new byte[length];
-            if (!read(is, jpeg, length)) {
-                return 0;
-            }
-
-            // Identify the byte order.
-            int tag = pack(jpeg, offset, 4, false);
-            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
-                Log.e(TAG, "Invalid byte order");
-                return 0;
-            }
-            boolean littleEndian = (tag == 0x49492A00);
-
-            // Get the offset and check if it is reasonable.
-            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
-            if (count < 10 || count > length) {
-                Log.e(TAG, "Invalid offset");
-                return 0;
-            }
-            offset += count;
-            length -= count;
-
-            // Get the count and go through all the elements.
-            count = pack(jpeg, offset - 2, 2, littleEndian);
-            while (count-- > 0 && length >= 12) {
-                // Get the tag and check if it is orientation.
-                tag = pack(jpeg, offset, 2, littleEndian);
-                if (tag == 0x0112) {
-                    // We do not really care about type and count, do we?
-                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
-                    switch (orientation) {
-                        case 1:
-                            return 0;
-                        case 3:
-                            return 180;
-                        case 6:
-                            return 90;
-                        case 8:
-                            return 270;
-                    }
-                    Log.i(TAG, "Unsupported orientation");
-                    return 0;
-                }
-                offset += 12;
-                length -= 12;
-            }
-        }
-
-        Log.i(TAG, "Orientation not found");
-        return 0;
-    }
-
-    private static int pack(byte[] bytes, int offset, int length,
-            boolean littleEndian) {
-        int step = 1;
-        if (littleEndian) {
-            offset += length - 1;
-            step = -1;
-        }
-
-        int value = 0;
-        while (length-- > 0) {
-            value = (value << 8) | (bytes[offset] & 0xFF);
-            offset += step;
-        }
-        return value;
-    }
-
-    private static boolean read(InputStream is, byte[] buf, int length) {
+        ExifInterface exif = new ExifInterface();
         try {
-            return is.read(buf, 0, length) == length;
-        } catch (IOException ex) {
-            return false;
+            exif.readExif(is);
+            Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
+            if (val == null) {
+                return 0;
+            } else {
+                return ExifInterface.getRotationForOrientationValue(val.shortValue());
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Failed to read EXIF orientation", e);
+            return 0;
         }
     }
 }
diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java
index 3f937e3..4756149 100644
--- a/src/com/android/gallery3d/data/ImageCacheRequest.java
+++ b/src/com/android/gallery3d/data/ImageCacheRequest.java
@@ -60,13 +60,11 @@
                 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                 Bitmap bitmap;
                 if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getMicroThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 } else {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 }
                 if (bitmap == null && !jc.isCancelled()) {
                     Log.w(TAG, "decode cached failed " + debugTag());
diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java
index e05aac0..7b7015a 100644
--- a/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/src/com/android/gallery3d/data/LocalAlbum.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Environment;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
@@ -29,9 +30,11 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.BucketNames;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.MediaSetUtils;
 
+import java.io.File;
 import java.util.ArrayList;
 
 // LocalAlbumSet lists all media items in one bucket on local storage.
@@ -61,7 +64,7 @@
         mApplication = application;
         mResolver = application.getContentResolver();
         mBucketId = bucketId;
-        mName = getLocalizedName(application.getResources(), bucketId, name);
+        mName = name;
         mIsImage = isImage;
 
         if (isImage) {
@@ -245,7 +248,7 @@
 
     @Override
     public String getName() {
-        return mName;
+        return getLocalizedName(mApplication.getResources(), mBucketId, mName);
     }
 
     @Override
@@ -290,4 +293,33 @@
             return name;
         }
     }
+
+    // Relative path is the absolute path minus external storage path
+    public static String getRelativePath(int bucketId) {
+        String relativePath = "/";
+        if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) {
+            relativePath += BucketNames.CAMERA;
+        } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) {
+            relativePath += BucketNames.DOWNLOAD;
+        } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) {
+            relativePath += BucketNames.IMPORTED;
+        } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) {
+            relativePath += BucketNames.SCREENSHOTS;
+        } else if (bucketId == MediaSetUtils.EDITED_ONLINE_PHOTOS_BUCKET_ID) {
+            relativePath += BucketNames.EDITED_ONLINE_PHOTOS;
+        } else {
+            // If the first few cases didn't hit the matching path, do a
+            // thorough search in the local directories.
+            File extStorage = Environment.getExternalStorageDirectory();
+            String path = GalleryUtils.searchDirForPath(extStorage, bucketId);
+            if (path == null) {
+                Log.w(TAG, "Relative path for bucket id: " + bucketId + " is not found.");
+                relativePath = null;
+            } else {
+                relativePath = path.substring(extStorage.getAbsolutePath().length());
+            }
+        }
+        return relativePath;
+    }
+
 }
diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java
index b2be124..1ed67ec 100644
--- a/src/com/android/gallery3d/data/LocalImage.java
+++ b/src/com/android/gallery3d/data/LocalImage.java
@@ -23,7 +23,6 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
-import android.media.ExifInterface;
 import android.net.Uri;
 import android.os.Build;
 import android.provider.MediaStore.Images;
@@ -36,13 +35,19 @@
 import com.android.gallery3d.app.StitchingProgressManager;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 import com.android.gallery3d.util.UpdateHelper;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel.MapMode;
 
 // LocalImage represents an image in the local storage.
 public class LocalImage extends LocalMediaItem {
@@ -190,15 +195,15 @@
 
             // try to decode from JPEG EXIF
             if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
-                ExifInterface exif = null;
-                byte [] thumbData = null;
+                ExifInterface exif = new ExifInterface();
+                byte[] thumbData = null;
                 try {
-                    exif = new ExifInterface(mLocalFilePath);
-                    if (exif != null) {
-                        thumbData = exif.getThumbnail();
-                    }
-                } catch (Throwable t) {
-                    Log.w(TAG, "fail to get exif thumb", t);
+                    exif.readExif(mLocalFilePath);
+                    thumbData = exif.getThumbnail();
+                } catch (FileNotFoundException e) {
+                    Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath);
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath);
                 }
                 if (thumbData != null) {
                     Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
@@ -270,21 +275,6 @@
                 new String[]{String.valueOf(id)});
     }
 
-    private static String getExifOrientation(int orientation) {
-        switch (orientation) {
-            case 0:
-                return String.valueOf(ExifInterface.ORIENTATION_NORMAL);
-            case 90:
-                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_90);
-            case 180:
-                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_180);
-            case 270:
-                return String.valueOf(ExifInterface.ORIENTATION_ROTATE_270);
-            default:
-                throw new AssertionError("invalid: " + orientation);
-        }
-    }
-
     @Override
     public void rotate(int degrees) {
         GalleryUtils.assertNotInRenderThread();
@@ -294,18 +284,23 @@
         if (rotation < 0) rotation += 360;
 
         if (mimeType.equalsIgnoreCase("image/jpeg")) {
-            try {
-                ExifInterface exif = new ExifInterface(filePath);
-                exif.setAttribute(ExifInterface.TAG_ORIENTATION,
-                        getExifOrientation(rotation));
-                exif.saveAttributes();
-            } catch (IOException e) {
-                Log.w(TAG, "cannot set exif data: " + filePath);
+            ExifInterface exifInterface = new ExifInterface();
+            ExifTag tag = exifInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+                    ExifInterface.getOrientationValueForRotation(rotation));
+            if(tag != null) {
+                exifInterface.setTag(tag);
+                try {
+                    exifInterface.forceRewriteExif(filePath);
+                    fileSize = new File(filePath).length();
+                    values.put(Images.Media.SIZE, fileSize);
+                } catch (FileNotFoundException e) {
+                    Log.w(TAG, "cannot find file to set exif: " + filePath);
+                } catch (IOException e) {
+                    Log.w(TAG, "cannot set exif data: " + filePath);
+                }
+            } else {
+                Log.w(TAG, "Could not build tag: " + ExifInterface.TAG_ORIENTATION);
             }
-
-            // We need to update the filesize as well
-            fileSize = new File(filePath).length();
-            values.put(Images.Media.SIZE, fileSize);
         }
 
         values.put(Images.Media.ORIENTATION, rotation);
diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java
index cbaf82f..f0b5e57 100644
--- a/src/com/android/gallery3d/data/LocalMergeAlbum.java
+++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java
@@ -41,7 +41,6 @@
     private final Comparator<MediaItem> mComparator;
     private final MediaSet[] mSources;
 
-    private String mName;
     private FetchCache[] mFetcher;
     private int mSupportedOperation;
     private int mBucketId;
@@ -54,7 +53,6 @@
         super(path, INVALID_DATA_VERSION);
         mComparator = comparator;
         mSources = sources;
-        mName = sources.length == 0 ? "" : sources[0].getName();
         mBucketId = bucketId;
         for (MediaSet set : mSources) {
             set.addContentListener(this);
@@ -82,7 +80,6 @@
         mSupportedOperation = supported;
         mIndex.clear();
         mIndex.put(0, new int[mSources.length]);
-        mName = mSources.length == 0 ? "" : mSources[0].getName();
     }
 
     private void invalidateCache() {
@@ -111,7 +108,7 @@
 
     @Override
     public String getName() {
-        return mName;
+        return mSources.length == 0 ? "" : mSources[0].getName();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java
index 44b8539..b1e1cb3 100644
--- a/src/com/android/gallery3d/data/LocalVideo.java
+++ b/src/com/android/gallery3d/data/LocalVideo.java
@@ -180,7 +180,7 @@
 
     @Override
     public int getSupportedOperations() {
-        return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM;
+        return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE;
     }
 
     @Override
diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java
index 2982247..cac524b 100644
--- a/src/com/android/gallery3d/data/MediaDetails.java
+++ b/src/com/android/gallery3d/data/MediaDetails.java
@@ -16,12 +16,16 @@
 
 package com.android.gallery3d.data;
 
-import android.media.ExifInterface;
-
 import com.android.gallery3d.R;
-import com.android.gallery3d.common.ExifTags;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
 
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map.Entry;
@@ -105,10 +109,18 @@
         return mUnits.get(index);
     }
 
-    private static void setExifData(MediaDetails details, ExifInterface exif, String tag,
+    private static void setExifData(MediaDetails details, ExifTag tag,
             int key) {
-        String value = exif.getAttribute(tag);
-        if (value != null) {
+        if (tag != null) {
+            String value = null;
+            int type = tag.getDataType();
+            if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) {
+                value = String.valueOf(tag.getValueAsRational(0).toDouble());
+            } else if (type == ExifTag.TYPE_ASCII) {
+                value = tag.getValueAsString();
+            } else {
+                value = String.valueOf(tag.forceGetValueAsLong(0));
+            }
             if (key == MediaDetails.INDEX_FLASH) {
                 MediaDetails.FlashState state = new MediaDetails.FlashState(
                         Integer.valueOf(value.toString()));
@@ -120,29 +132,39 @@
     }
 
     public static void extractExifInfo(MediaDetails details, String filePath) {
-        try {
-            ExifInterface exif = new ExifInterface(filePath);
-            setExifData(details, exif, ExifInterface.TAG_FLASH, MediaDetails.INDEX_FLASH);
-            setExifData(details, exif, ExifInterface.TAG_IMAGE_WIDTH, MediaDetails.INDEX_WIDTH);
-            setExifData(details, exif, ExifInterface.TAG_IMAGE_LENGTH,
-                    MediaDetails.INDEX_HEIGHT);
-            setExifData(details, exif, ExifInterface.TAG_MAKE, MediaDetails.INDEX_MAKE);
-            setExifData(details, exif, ExifInterface.TAG_MODEL, MediaDetails.INDEX_MODEL);
-            setExifData(details, exif, ExifTags.TAG_APERTURE, MediaDetails.INDEX_APERTURE);
-            setExifData(details, exif, ExifTags.TAG_ISO, MediaDetails.INDEX_ISO);
-            setExifData(details, exif, ExifInterface.TAG_WHITE_BALANCE,
-                    MediaDetails.INDEX_WHITE_BALANCE);
-            setExifData(details, exif, ExifTags.TAG_EXPOSURE_TIME,
-                    MediaDetails.INDEX_EXPOSURE_TIME);
 
-            double data = exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, 0);
-            if (data != 0f) {
-                details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, data);
-                details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
-            }
-        } catch (IOException ex) {
-            // ignore it.
-            Log.w(TAG, "", ex);
+        ExifInterface exif = new ExifInterface();
+        try {
+            exif.readExif(filePath);
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, "Could not find file to read exif: " + filePath, e);
+        } catch (IOException e) {
+            Log.w(TAG, "Could not read exif from file: " + filePath, e);
+        }
+
+        setExifData(details, exif.getTag(ExifInterface.TAG_FLASH),
+                MediaDetails.INDEX_FLASH);
+        setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_WIDTH),
+                MediaDetails.INDEX_WIDTH);
+        setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH),
+                MediaDetails.INDEX_HEIGHT);
+        setExifData(details, exif.getTag(ExifInterface.TAG_MAKE),
+                MediaDetails.INDEX_MAKE);
+        setExifData(details, exif.getTag(ExifInterface.TAG_MODEL),
+                MediaDetails.INDEX_MODEL);
+        setExifData(details, exif.getTag(ExifInterface.TAG_APERTURE_VALUE),
+                MediaDetails.INDEX_APERTURE);
+        setExifData(details, exif.getTag(ExifInterface.TAG_ISO_SPEED_RATINGS),
+                MediaDetails.INDEX_ISO);
+        setExifData(details, exif.getTag(ExifInterface.TAG_WHITE_BALANCE),
+                MediaDetails.INDEX_WHITE_BALANCE);
+        setExifData(details, exif.getTag(ExifInterface.TAG_EXPOSURE_TIME),
+                MediaDetails.INDEX_EXPOSURE_TIME);
+        ExifTag focalTag = exif.getTag(ExifInterface.TAG_FOCAL_LENGTH);
+        if (focalTag != null) {
+            details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH,
+                    focalTag.getValueAsRational(0).toDouble());
+            details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm);
         }
     }
 }
diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java
index 19084d4..59ea865 100644
--- a/src/com/android/gallery3d/data/MediaItem.java
+++ b/src/com/android/gallery3d/data/MediaItem.java
@@ -42,15 +42,10 @@
     private static final int BYTESBUFFER_SIZE = 200 * 1024;
 
     private static int sMicrothumbnailTargetSize = 200;
-    private static BitmapPool sMicroThumbPool;
     private static final BytesBufferPool sMicroThumbBufferPool =
             new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
 
     private static int sThumbnailTargetSize = 640;
-    private static final BitmapPool sThumbPool =
-            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-            ? new BitmapPool(4)
-            : null;
 
     // TODO: fix default value for latlng and change this.
     public static final double INVALID_LATLNG = 0f;
@@ -126,33 +121,14 @@
         }
     }
 
-    public static BitmapPool getMicroThumbPool() {
-        if (ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY && sMicroThumbPool == null) {
-            initializeMicroThumbPool();
-        }
-        return sMicroThumbPool;
-    }
-
-    public static BitmapPool getThumbPool() {
-        return sThumbPool;
-    }
-
     public static BytesBufferPool getBytesBufferPool() {
         return sMicroThumbBufferPool;
     }
 
-    private static void initializeMicroThumbPool() {
-        sMicroThumbPool =
-                ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-                ? new BitmapPool(sMicrothumbnailTargetSize, sMicrothumbnailTargetSize, 16)
-                : null;
-    }
-
     public static void setThumbnailSizes(int size, int microSize) {
         sThumbnailTargetSize = size;
         if (sMicrothumbnailTargetSize != microSize) {
             sMicrothumbnailTargetSize = microSize;
-            initializeMicroThumbPool();
         }
     }
 }
diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java
index a41b275..270d4cf 100644
--- a/src/com/android/gallery3d/data/MediaObject.java
+++ b/src/com/android/gallery3d/data/MediaObject.java
@@ -35,12 +35,12 @@
     public static final int SUPPORT_CACHE = 1 << 8;
     public static final int SUPPORT_EDIT = 1 << 9;
     public static final int SUPPORT_INFO = 1 << 10;
-    public static final int SUPPORT_IMPORT = 1 << 11;
-    public static final int SUPPORT_TRIM = 1 << 12;
-    public static final int SUPPORT_UNLOCK = 1 << 13;
-    public static final int SUPPORT_BACK = 1 << 14;
-    public static final int SUPPORT_ACTION = 1 << 15;
-    public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16;
+    public static final int SUPPORT_TRIM = 1 << 11;
+    public static final int SUPPORT_UNLOCK = 1 << 12;
+    public static final int SUPPORT_BACK = 1 << 13;
+    public static final int SUPPORT_ACTION = 1 << 14;
+    public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 15;
+    public static final int SUPPORT_MUTE = 1 << 16;
     public static final int SUPPORT_ALL = 0xffffffff;
 
     // These are the bits returned from getMediaType():
@@ -119,10 +119,6 @@
         return MEDIA_TYPE_UNKNOWN;
     }
 
-    public boolean Import() {
-        throw new UnsupportedOperationException();
-    }
-
     public MediaDetails getDetails() {
         MediaDetails details = new MediaDetails();
         return details;
diff --git a/src/com/android/gallery3d/data/MediaSet.java b/src/com/android/gallery3d/data/MediaSet.java
index 87b5f56..683aa6b 100644
--- a/src/com/android/gallery3d/data/MediaSet.java
+++ b/src/com/android/gallery3d/data/MediaSet.java
@@ -156,16 +156,10 @@
     // listener is automatically removed when there is no other reference to
     // the listener.
     public void addContentListener(ContentListener listener) {
-        if (mListeners.containsKey(listener)) {
-            throw new IllegalArgumentException();
-        }
         mListeners.put(listener, null);
     }
 
     public void removeContentListener(ContentListener listener) {
-        if (!mListeners.containsKey(listener)) {
-            throw new IllegalArgumentException();
-        }
         mListeners.remove(listener);
     }
 
diff --git a/src/com/android/gallery3d/data/MtpContext.java b/src/com/android/gallery3d/data/MtpContext.java
deleted file mode 100644
index 53b6faa..0000000
--- a/src/com/android/gallery3d/data/MtpContext.java
+++ /dev/null
@@ -1,144 +0,0 @@
-package com.android.gallery3d.data;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.mtp.MtpObjectInfo;
-import android.net.Uri;
-import android.os.Environment;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.util.BucketNames;
-import com.android.gallery3d.util.GalleryUtils;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
-public class MtpContext implements MtpClient.Listener {
-    private static final String TAG = "MtpContext";
-
-    private ScannerClient mScannerClient;
-    private Context mContext;
-    private MtpClient mClient;
-
-    private static final class ScannerClient implements MediaScannerConnectionClient {
-        ArrayList<String> mPaths = new ArrayList<String>();
-        MediaScannerConnection mScannerConnection;
-        boolean mConnected;
-        Object mLock = new Object();
-
-        public ScannerClient(Context context) {
-            mScannerConnection = new MediaScannerConnection(context, this);
-        }
-
-        public void scanPath(String path) {
-            synchronized (mLock) {
-                if (mConnected) {
-                    mScannerConnection.scanFile(path, null);
-                } else {
-                    mPaths.add(path);
-                    mScannerConnection.connect();
-                }
-            }
-        }
-
-        @Override
-        public void onMediaScannerConnected() {
-            synchronized (mLock) {
-                mConnected = true;
-                if (!mPaths.isEmpty()) {
-                    for (String path : mPaths) {
-                        mScannerConnection.scanFile(path, null);
-                    }
-                    mPaths.clear();
-                }
-            }
-        }
-
-        @Override
-        public void onScanCompleted(String path, Uri uri) {
-        }
-    }
-
-    public MtpContext(Context context) {
-        mContext = context;
-        mScannerClient = new ScannerClient(context);
-        mClient = new MtpClient(mContext);
-    }
-
-    public void pause() {
-        mClient.removeListener(this);
-    }
-
-    public void resume() {
-        mClient.addListener(this);
-        notifyDirty();
-    }
-
-    @Override
-    public void deviceAdded(android.mtp.MtpDevice device) {
-        notifyDirty();
-        showToast(R.string.camera_connected);
-    }
-
-    @Override
-    public void deviceRemoved(android.mtp.MtpDevice device) {
-        notifyDirty();
-        showToast(R.string.camera_disconnected);
-    }
-
-    private void notifyDirty() {
-        mContext.getContentResolver().notifyChange(Uri.parse("mtp://"), null);
-    }
-
-    private void showToast(final int msg) {
-        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    public MtpClient getMtpClient() {
-        return mClient;
-    }
-
-    public boolean copyFile(String deviceName, MtpObjectInfo objInfo) {
-        if (GalleryUtils.hasSpaceForSize(objInfo.getCompressedSize())) {
-            File dest = Environment.getExternalStorageDirectory();
-            dest = new File(dest, BucketNames.IMPORTED);
-            dest.mkdirs();
-            String destPath = new File(dest, objInfo.getName()).getAbsolutePath();
-            int objectId = objInfo.getObjectHandle();
-            if (mClient.importFile(deviceName, objectId, destPath)) {
-                mScannerClient.scanPath(destPath);
-                return true;
-            }
-        } else {
-            Log.w(TAG, "No space to import " + objInfo.getName() +
-                    " whose size = " + objInfo.getCompressedSize());
-        }
-        return false;
-    }
-
-    public boolean copyAlbum(String deviceName, String albumName,
-            List<MtpObjectInfo> children) {
-        File dest = Environment.getExternalStorageDirectory();
-        dest = new File(dest, albumName);
-        dest.mkdirs();
-        int success = 0;
-        for (MtpObjectInfo child : children) {
-            if (!GalleryUtils.hasSpaceForSize(child.getCompressedSize())) continue;
-
-            File importedFile = new File(dest, child.getName());
-            String path = importedFile.getAbsolutePath();
-            if (mClient.importFile(deviceName, child.getObjectHandle(), path)) {
-                mScannerClient.scanPath(path);
-                success++;
-            }
-        }
-        return success == children.size();
-    }
-}
diff --git a/src/com/android/gallery3d/data/MtpDevice.java b/src/com/android/gallery3d/data/MtpDevice.java
deleted file mode 100644
index 25ffbc8..0000000
--- a/src/com/android/gallery3d/data/MtpDevice.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.data;
-
-import android.annotation.TargetApi;
-import android.hardware.usb.UsbDevice;
-import android.mtp.MtpConstants;
-import android.mtp.MtpObjectInfo;
-import android.mtp.MtpStorageInfo;
-import android.net.Uri;
-import android.util.Log;
-
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.ApiHelper;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
-public class MtpDevice extends MediaSet {
-    private static final String TAG = "MtpDevice";
-
-    private final GalleryApp mApplication;
-    private final int mDeviceId;
-    private final String mDeviceName;
-    private final MtpContext mMtpContext;
-    private final String mName;
-    private final ChangeNotifier mNotifier;
-    private final Path mItemPath;
-    private List<MtpObjectInfo> mJpegChildren;
-
-    public MtpDevice(Path path, GalleryApp application, int deviceId,
-            String name, MtpContext mtpContext) {
-        super(path, nextVersionNumber());
-        mApplication = application;
-        mDeviceId = deviceId;
-        mDeviceName = UsbDevice.getDeviceName(deviceId);
-        mMtpContext = mtpContext;
-        mName = name;
-        mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application);
-        mItemPath = Path.fromString("/mtp/item/" + String.valueOf(deviceId));
-        mJpegChildren = new ArrayList<MtpObjectInfo>();
-    }
-
-    public MtpDevice(Path path, GalleryApp application, int deviceId,
-            MtpContext mtpContext) {
-        this(path, application, deviceId,
-                MtpDeviceSet.getDeviceName(mtpContext, deviceId), mtpContext);
-    }
-
-    private List<MtpObjectInfo> loadItems() {
-        ArrayList<MtpObjectInfo> result = new ArrayList<MtpObjectInfo>();
-
-        List<MtpStorageInfo> storageList = mMtpContext.getMtpClient()
-                 .getStorageList(mDeviceName);
-        if (storageList == null) return result;
-
-        for (MtpStorageInfo info : storageList) {
-            collectJpegChildren(info.getStorageId(), 0, result);
-        }
-
-        return result;
-    }
-
-    private void collectJpegChildren(int storageId, int objectId,
-            ArrayList<MtpObjectInfo> result) {
-        ArrayList<MtpObjectInfo> dirChildren = new ArrayList<MtpObjectInfo>();
-
-        queryChildren(storageId, objectId, result, dirChildren);
-
-        for (int i = 0, n = dirChildren.size(); i < n; i++) {
-            MtpObjectInfo info = dirChildren.get(i);
-            collectJpegChildren(storageId, info.getObjectHandle(), result);
-        }
-    }
-
-    private void queryChildren(int storageId, int objectId,
-            ArrayList<MtpObjectInfo> jpeg, ArrayList<MtpObjectInfo> dir) {
-        List<MtpObjectInfo> children = mMtpContext.getMtpClient().getObjectList(
-                mDeviceName, storageId, objectId);
-        if (children == null) return;
-
-        for (MtpObjectInfo obj : children) {
-            int format = obj.getFormat();
-            switch (format) {
-                case MtpConstants.FORMAT_JFIF:
-                case MtpConstants.FORMAT_EXIF_JPEG:
-                    jpeg.add(obj);
-                    break;
-                case MtpConstants.FORMAT_ASSOCIATION:
-                    dir.add(obj);
-                    break;
-                default:
-                    Log.w(TAG, "other type: name = " + obj.getName()
-                            + ", format = " + format);
-            }
-        }
-    }
-
-    public static MtpObjectInfo getObjectInfo(MtpContext mtpContext, int deviceId,
-            int objectId) {
-        String deviceName = UsbDevice.getDeviceName(deviceId);
-        return mtpContext.getMtpClient().getObjectInfo(deviceName, objectId);
-    }
-
-    @Override
-    public ArrayList<MediaItem> getMediaItem(int start, int count) {
-        ArrayList<MediaItem> result = new ArrayList<MediaItem>();
-        int begin = start;
-        int end = Math.min(start + count, mJpegChildren.size());
-
-        DataManager dataManager = mApplication.getDataManager();
-        for (int i = begin; i < end; i++) {
-            MtpObjectInfo child = mJpegChildren.get(i);
-            Path childPath = mItemPath.getChild(child.getObjectHandle());
-            synchronized (DataManager.LOCK) {
-                MtpImage image = (MtpImage) dataManager.peekMediaObject(childPath);
-                if (image == null) {
-                    image = new MtpImage(
-                            childPath, mApplication, mDeviceId, child, mMtpContext);
-                } else {
-                    image.updateContent(child);
-                }
-                result.add(image);
-            }
-        }
-        return result;
-    }
-
-    @Override
-    public int getMediaItemCount() {
-        return mJpegChildren.size();
-    }
-
-    @Override
-    public String getName() {
-        return mName;
-    }
-
-    @Override
-    public long reload() {
-        if (mNotifier.isDirty()) {
-            mDataVersion = nextVersionNumber();
-            mJpegChildren = loadItems();
-        }
-        return mDataVersion;
-    }
-
-    @Override
-    public int getSupportedOperations() {
-        return SUPPORT_IMPORT;
-    }
-
-    @Override
-    public boolean Import() {
-        return mMtpContext.copyAlbum(mDeviceName, mName, mJpegChildren);
-    }
-
-    @Override
-    public boolean isLeafAlbum() {
-        return true;
-    }
-}
diff --git a/src/com/android/gallery3d/data/MtpDeviceSet.java b/src/com/android/gallery3d/data/MtpDeviceSet.java
deleted file mode 100644
index bc4bc63..0000000
--- a/src/com/android/gallery3d/data/MtpDeviceSet.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.data;
-
-import android.annotation.TargetApi;
-import android.mtp.MtpDeviceInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.util.Log;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.FutureListener;
-import com.android.gallery3d.util.MediaSetUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-// MtpDeviceSet -- MtpDevice -- MtpImage
-@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
-public class MtpDeviceSet extends MediaSet
-        implements FutureListener<ArrayList<MediaSet>> {
-    private static final String TAG = "MtpDeviceSet";
-
-    private GalleryApp mApplication;
-    private final ChangeNotifier mNotifier;
-    private final MtpContext mMtpContext;
-    private final String mName;
-    private final Handler mHandler;
-
-    private Future<ArrayList<MediaSet>> mLoadTask;
-    private ArrayList<MediaSet> mDeviceSet = new ArrayList<MediaSet>();
-    private ArrayList<MediaSet> mLoadBuffer;
-    private boolean mIsLoading;
-
-    public MtpDeviceSet(Path path, GalleryApp application, MtpContext mtpContext) {
-        super(path, nextVersionNumber());
-        mApplication = application;
-        mNotifier = new ChangeNotifier(this, Uri.parse("mtp://"), application);
-        mMtpContext = mtpContext;
-        mName = application.getResources().getString(R.string.set_label_mtp_devices);
-        mHandler = new Handler(mApplication.getMainLooper());
-    }
-
-    private class DevicesLoader implements Job<ArrayList<MediaSet>> {
-        @Override
-        public ArrayList<MediaSet> run(JobContext jc) {
-            DataManager dataManager = mApplication.getDataManager();
-            ArrayList<MediaSet> result = new ArrayList<MediaSet>();
-
-            // Enumerate all devices
-            List<android.mtp.MtpDevice> devices = mMtpContext.getMtpClient().getDeviceList();
-            Log.v(TAG, "loadDevices: " + devices + ", size=" + devices.size());
-            for (android.mtp.MtpDevice mtpDevice : devices) {
-                synchronized (DataManager.LOCK) {
-                    int deviceId = mtpDevice.getDeviceId();
-                    Path childPath = mPath.getChild(deviceId);
-                    MtpDevice device = (MtpDevice) dataManager.peekMediaObject(childPath);
-                    if (device == null) {
-                        device = new MtpDevice(childPath, mApplication, deviceId, mMtpContext);
-                    }
-                    Log.d(TAG, "add device " + device);
-                    result.add(device);
-                }
-            }
-            Collections.sort(result, MediaSetUtils.NAME_COMPARATOR);
-            return result;
-        }
-    }
-
-    public static String getDeviceName(MtpContext mtpContext, int deviceId) {
-        android.mtp.MtpDevice device = mtpContext.getMtpClient().getDevice(deviceId);
-        if (device == null) {
-            return "";
-        }
-        MtpDeviceInfo info = device.getDeviceInfo();
-        if (info == null) {
-            return "";
-        }
-        String manufacturer = info.getManufacturer().trim();
-        String model = info.getModel().trim();
-        return manufacturer + " " + model;
-    }
-
-    @Override
-    public MediaSet getSubMediaSet(int index) {
-        return index < mDeviceSet.size() ? mDeviceSet.get(index) : null;
-    }
-
-    @Override
-    public int getSubMediaSetCount() {
-        return mDeviceSet.size();
-    }
-
-    @Override
-    public String getName() {
-        return mName;
-    }
-
-    @Override
-    public synchronized boolean isLoading() {
-        return mIsLoading;
-    }
-
-    @Override
-    public synchronized long reload() {
-        if (mNotifier.isDirty()) {
-            if (mLoadTask != null) mLoadTask.cancel();
-            mIsLoading = true;
-            mLoadTask = mApplication.getThreadPool().submit(new DevicesLoader(), this);
-        }
-        if (mLoadBuffer != null) {
-            mDeviceSet = mLoadBuffer;
-            mLoadBuffer = null;
-            for (MediaSet device : mDeviceSet) {
-                device.reload();
-            }
-            mDataVersion = nextVersionNumber();
-        }
-        return mDataVersion;
-    }
-
-    @Override
-    public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) {
-        if (future != mLoadTask) return;
-        mLoadBuffer = future.get();
-        mIsLoading = false;
-        if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>();
-
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                notifyContentChanged();
-            }
-        });
-    }
-}
diff --git a/src/com/android/gallery3d/data/MtpImage.java b/src/com/android/gallery3d/data/MtpImage.java
deleted file mode 100644
index 0abb198..0000000
--- a/src/com/android/gallery3d/data/MtpImage.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.data;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapRegionDecoder;
-import android.hardware.usb.UsbDevice;
-import android.mtp.MtpObjectInfo;
-import android.net.Uri;
-import android.util.Log;
-
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.provider.GalleryProvider;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import java.text.DateFormat;
-import java.util.Date;
-
-@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB_MR1)
-public class MtpImage extends MediaItem {
-    private static final String TAG = "MtpImage";
-
-    private final int mDeviceId;
-    private int mObjectId;
-    private int mObjectSize;
-    private long mDateTaken;
-    private String mFileName;
-    private final MtpContext mMtpContext;
-    private final MtpObjectInfo mObjInfo;
-    private final int mImageWidth;
-    private final int mImageHeight;
-    private final Context mContext;
-
-    MtpImage(Path path, GalleryApp application, int deviceId,
-            MtpObjectInfo objInfo, MtpContext mtpContext) {
-        super(path, nextVersionNumber());
-        mContext = application.getAndroidContext();
-        mDeviceId = deviceId;
-        mObjInfo = objInfo;
-        mObjectId = objInfo.getObjectHandle();
-        mObjectSize = objInfo.getCompressedSize();
-        mDateTaken = objInfo.getDateCreated();
-        mFileName = objInfo.getName();
-        mImageWidth = objInfo.getImagePixWidth();
-        mImageHeight = objInfo.getImagePixHeight();
-        mMtpContext = mtpContext;
-    }
-
-    MtpImage(Path path, GalleryApp app, int deviceId, int objectId, MtpContext mtpContext) {
-        this(path, app, deviceId, MtpDevice.getObjectInfo(mtpContext, deviceId, objectId),
-                mtpContext);
-    }
-
-    @Override
-    public long getDateInMs() {
-        return mDateTaken;
-    }
-
-    @Override
-    public Job<Bitmap> requestImage(int type) {
-        return new Job<Bitmap>() {
-            @Override
-            public Bitmap run(JobContext jc) {
-                byte[] thumbnail = mMtpContext.getMtpClient().getThumbnail(
-                        UsbDevice.getDeviceName(mDeviceId), mObjectId);
-                if (thumbnail == null) {
-                    Log.w(TAG, "decoding thumbnail failed");
-                    return null;
-                }
-                return DecodeUtils.decode(jc, thumbnail, null);
-            }
-        };
-    }
-
-    @Override
-    public Job<BitmapRegionDecoder> requestLargeImage() {
-        return new Job<BitmapRegionDecoder>() {
-            @Override
-            public BitmapRegionDecoder run(JobContext jc) {
-                byte[] bytes = mMtpContext.getMtpClient().getObject(
-                        UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize);
-                return DecodeUtils.createBitmapRegionDecoder(
-                        jc, bytes, 0, bytes.length, false);
-            }
-        };
-    }
-
-    public byte[] getImageData() {
-        return mMtpContext.getMtpClient().getObject(
-                UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize);
-    }
-
-    @Override
-    public boolean Import() {
-        return mMtpContext.copyFile(UsbDevice.getDeviceName(mDeviceId), mObjInfo);
-    }
-
-    @Override
-    public int getSupportedOperations() {
-        return SUPPORT_FULL_IMAGE | SUPPORT_IMPORT;
-    }
-
-    public void updateContent(MtpObjectInfo info) {
-        if (mObjectId != info.getObjectHandle() || mDateTaken != info.getDateCreated()) {
-            mObjectId = info.getObjectHandle();
-            mDateTaken = info.getDateCreated();
-            mDataVersion = nextVersionNumber();
-        }
-    }
-
-    @Override
-    public String getMimeType() {
-        // Currently only JPEG is supported in MTP.
-        return "image/jpeg";
-    }
-
-    @Override
-    public int getMediaType() {
-        return MEDIA_TYPE_IMAGE;
-    }
-
-    @Override
-    public long getSize() {
-        return mObjectSize;
-    }
-
-    @Override
-    public Uri getContentUri() {
-        return GalleryProvider.getUriFor(mContext, mPath);
-    }
-
-    @Override
-    public MediaDetails getDetails() {
-        MediaDetails details = super.getDetails();
-        DateFormat formater = DateFormat.getDateTimeInstance();
-        details.addDetail(MediaDetails.INDEX_TITLE, mFileName);
-        details.addDetail(MediaDetails.INDEX_DATETIME, formater.format(new Date(mDateTaken)));
-        details.addDetail(MediaDetails.INDEX_WIDTH, mImageWidth);
-        details.addDetail(MediaDetails.INDEX_HEIGHT, mImageHeight);
-        details.addDetail(MediaDetails.INDEX_SIZE, Long.valueOf(mObjectSize));
-        return details;
-    }
-
-    @Override
-    public int getWidth() {
-        return mImageWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        return mImageHeight;
-    }
-}
diff --git a/src/com/android/gallery3d/data/MtpSource.java b/src/com/android/gallery3d/data/MtpSource.java
deleted file mode 100644
index 47a2e6c..0000000
--- a/src/com/android/gallery3d/data/MtpSource.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.data;
-
-import com.android.gallery3d.app.GalleryApp;
-
-public class MtpSource extends MediaSource {
-    @SuppressWarnings("unused")
-    private static final String TAG = "MtpSource";
-
-    private static final int MTP_DEVICESET = 0;
-    private static final int MTP_DEVICE = 1;
-    private static final int MTP_ITEM = 2;
-
-    GalleryApp mApplication;
-    PathMatcher mMatcher;
-    MtpContext mMtpContext;
-
-    public MtpSource(GalleryApp application) {
-        super("mtp");
-        mApplication = application;
-        mMatcher = new PathMatcher();
-        mMatcher.add("/mtp", MTP_DEVICESET);
-        mMatcher.add("/mtp/*", MTP_DEVICE);
-        mMatcher.add("/mtp/item/*/*", MTP_ITEM);
-        mMtpContext = new MtpContext(mApplication.getAndroidContext());
-    }
-
-    @Override
-    public MediaObject createMediaObject(Path path) {
-        switch (mMatcher.match(path)) {
-            case MTP_DEVICESET: {
-                return new MtpDeviceSet(path, mApplication, mMtpContext);
-            }
-            case MTP_DEVICE: {
-                int deviceId = mMatcher.getIntVar(0);
-                return new MtpDevice(path, mApplication, deviceId, mMtpContext);
-            }
-            case MTP_ITEM: {
-                int deviceId = mMatcher.getIntVar(0);
-                int objectId = mMatcher.getIntVar(1);
-                return new MtpImage(path, mApplication, deviceId, objectId, mMtpContext);
-            }
-            default:
-                throw new RuntimeException("bad path: " + path);
-        }
-    }
-
-    @Override
-    public void pause() {
-        mMtpContext.pause();
-    }
-
-    @Override
-    public void resume() {
-        mMtpContext.resume();
-    }
-
-    public static boolean isMtpPath(String s) {
-        return s != null && Path.fromString(s).getPrefix().equals("mtp");
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
new file mode 100644
index 0000000..30d4dde
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/EditorPlaceHolder.java
@@ -0,0 +1,92 @@
+package com.android.gallery3d.filtershow;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public class EditorPlaceHolder {
+    private static final String LOGTAG = "EditorPlaceHolder";
+
+    private FilterShowActivity mActivity = null;
+    private FrameLayout mContainer = null;
+    private HashMap<Integer, Editor> mEditors = new HashMap<Integer, Editor>();
+    private Vector<ImageShow> mOldViews = new Vector<ImageShow>();
+    private ImageLoader mImageLoader = null;
+
+    public EditorPlaceHolder(FilterShowActivity activity) {
+        mActivity = activity;
+    }
+
+    public void setContainer(FrameLayout container) {
+        mContainer = container;
+    }
+
+    public void addEditor(Editor c) {
+        mEditors.put(c.getID(), c);
+    }
+
+    public boolean contains(int type) {
+        if (mEditors.get(type) != null) {
+            return true;
+        }
+        return false;
+    }
+
+    public Editor showEditor(int type) {
+        Editor editor = mEditors.get(type);
+        if (editor == null) {
+            return null;
+        }
+
+        try {
+            editor.createEditor(mActivity, mContainer);
+            editor.setImageLoader(mImageLoader);
+            mContainer.setVisibility(View.VISIBLE);
+            mContainer.removeAllViews();
+            View eview = editor.getTopLevelView();
+            ViewParent parent = eview.getParent();
+
+            if (parent != null && parent instanceof FrameLayout) {
+                ((FrameLayout) parent).removeAllViews();
+            }
+
+            mContainer.addView(eview);
+            hideOldViews();
+            editor.setVisibility(View.VISIBLE);
+            return editor;
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public void setOldViews(Vector<ImageShow> views) {
+        mOldViews = views;
+    }
+
+    public void hide() {
+        mContainer.setVisibility(View.GONE);
+    }
+
+    public void hideOldViews() {
+        for (View view : mOldViews) {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    public void setImageLoader(ImageLoader imageLoader) {
+        mImageLoader = imageLoader;
+    }
+
+    public Editor getEditor(int editorId) {
+        return mEditors.get(editorId);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index ff78bd1..67ae3b5 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -16,22 +16,25 @@
 
 package com.android.gallery3d.filtershow;
 
-import android.annotation.TargetApi;
 import android.app.ActionBar;
-import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.ProgressDialog;
+import android.app.WallpaperManager;
 import android.content.ContentValues;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentTransaction;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.TypedValue;
@@ -43,98 +46,68 @@
 import android.view.WindowManager;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.SeekBar;
+import android.widget.FrameLayout;
 import android.widget.ShareActionProvider;
 import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
-import android.widget.Toast;
 
+import android.widget.Toast;
 import com.android.gallery3d.R;
 import com.android.gallery3d.data.LocalAlbum;
+import com.android.gallery3d.filtershow.cache.CachingPipeline;
+import com.android.gallery3d.filtershow.cache.FilteringPipeline;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterBorder;
-import com.android.gallery3d.filtershow.filters.ImageFilterBwFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterContrast;
-import com.android.gallery3d.filtershow.filters.ImageFilterExposure;
-import com.android.gallery3d.filtershow.filters.ImageFilterFx;
-import com.android.gallery3d.filtershow.filters.ImageFilterHue;
-import com.android.gallery3d.filtershow.filters.ImageFilterParametricBorder;
-import com.android.gallery3d.filtershow.filters.ImageFilterRS;
-import com.android.gallery3d.filtershow.filters.ImageFilterSaturated;
-import com.android.gallery3d.filtershow.filters.ImageFilterShadows;
-import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
-import com.android.gallery3d.filtershow.filters.ImageFilterVibrance;
-import com.android.gallery3d.filtershow.filters.ImageFilterVignette;
-import com.android.gallery3d.filtershow.filters.ImageFilterWBalance;
-import com.android.gallery3d.filtershow.imageshow.ImageBorder;
+import com.android.gallery3d.filtershow.category.*;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.editors.*;
+import com.android.gallery3d.filtershow.filters.*;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
 import com.android.gallery3d.filtershow.imageshow.ImageCrop;
-import com.android.gallery3d.filtershow.imageshow.ImageFlip;
-import com.android.gallery3d.filtershow.imageshow.ImageRotate;
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.imageshow.ImageSmallBorder;
-import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter;
-import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
-import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
-import com.android.gallery3d.filtershow.imageshow.ImageWithIcon;
-import com.android.gallery3d.filtershow.imageshow.ImageZoom;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 import com.android.gallery3d.filtershow.provider.SharedImageProvider;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+import com.android.gallery3d.filtershow.tools.BitmapTask;
 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
 import com.android.gallery3d.filtershow.ui.FramedTextButton;
-import com.android.gallery3d.filtershow.ui.ImageButtonTitle;
-import com.android.gallery3d.filtershow.ui.ImageCurves;
 import com.android.gallery3d.filtershow.ui.Spline;
 import com.android.gallery3d.util.GalleryUtils;
+import com.android.photos.data.GalleryBitmapPool;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.Vector;
 
-@TargetApi(16)
-public class FilterShowActivity extends Activity implements OnItemClickListener,
+public class FilterShowActivity extends FragmentActivity implements OnItemClickListener,
         OnShareTargetSelectedListener {
 
-    public static final String CROP_ACTION = "com.android.camera.action.EDITOR_CROP";
+    // fields for supporting crop action
+    public static final String CROP_ACTION = "com.android.camera.action.CROP";
+    private CropExtras mCropExtras = null;
+    private String mAction = "";
+    MasterImage mMasterImage = null;
+
+    private static final long LIMIT_SUPPORTS_HIGHRES = 134217728; // 128Mb
+
     public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET";
     public static final String LAUNCH_FULLSCREEN = "launch-fullscreen";
-    private final PanelController mPanelController = new PanelController();
+    public static final int MAX_BMAP_IN_INTENT = 990000;
     private ImageLoader mImageLoader = null;
     private ImageShow mImageShow = null;
-    private ImageCurves mImageCurves = null;
-    private ImageBorder mImageBorders = null;
-    private ImageStraighten mImageStraighten = null;
-    private ImageZoom mImageZoom = null;
-    private ImageCrop mImageCrop = null;
-    private ImageRotate mImageRotate = null;
-    private ImageFlip mImageFlip = null;
-    private ImageTinyPlanet mImageTinyPlanet = null;
 
-    private View mListFx = null;
-    private View mListBorders = null;
-    private View mListGeometry = null;
-    private View mListColors = null;
-    private View mListFilterButtons = null;
+    private View mSaveButton = null;
 
-    private ImageButton mFxButton = null;
-    private ImageButton mBorderButton = null;
-    private ImageButton mGeometryButton = null;
-    private ImageButton mColorsButton = null;
+    private EditorPlaceHolder mEditorPlaceHolder = new EditorPlaceHolder(this);
 
-    private ImageSmallFilter mCurrentImageSmallFilter = null;
     private static final int SELECT_PICTURE = 1;
     private static final String LOGTAG = "FilterShowActivity";
     protected static final boolean ANIMATE_PANELS = true;
-    private static int mImageBorderSize = 4; // in percent
 
-    private boolean mShowingHistoryPanel = false;
+    private boolean mShowingTinyPlanet = false;
     private boolean mShowingImageStatePanel = false;
 
     private final Vector<ImageShow> mImageViews = new Vector<ImageShow>();
-    private final Vector<View> mListViews = new Vector<View>();
-    private final Vector<ImageButton> mBottomPanelButtons = new Vector<ImageButton>();
 
     private ShareActionProvider mShareActionProvider;
     private File mSharedOutputFile = null;
@@ -142,22 +115,214 @@
     private boolean mSharingImage = false;
 
     private WeakReference<ProgressDialog> mSavingProgressDialog;
-    private static final int SEEK_BAR_MAX = 600;
 
     private LoadBitmapTask mLoadBitmapTask;
-    private ImageSmallFilter mNullFxFilter;
-    private ImageSmallFilter mNullBorderFilter;
+    private boolean mLoading = true;
+
+    private CategoryAdapter mCategoryLooksAdapter = null;
+    private CategoryAdapter mCategoryBordersAdapter = null;
+    private CategoryAdapter mCategoryGeometryAdapter = null;
+    private CategoryAdapter mCategoryFiltersAdapter = null;
+    private int mCurrentPanel = MainPanel.LOOKS;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        ImageFilterRS.setRenderScriptContext(this);
+        boolean onlyUsePortrait = getResources().getBoolean(R.bool.only_use_portrait);
+        if (onlyUsePortrait) {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+        }
+        MasterImage.setMaster(mMasterImage);
 
-        ImageShow.setDefaultBackgroundColor(getResources().getColor(R.color.background_screen));
-        ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor(R.color.background_main_toolbar));
+        clearGalleryBitmapPool();
+
+        CachingPipeline.createRenderscriptContext(this);
+        setupMasterImage();
+        setDefaultValues();
+        fillEditors();
+
+        loadXML();
+        loadMainPanel();
+
+        setDefaultPreset();
+
+        processIntent();
+    }
+
+    public boolean isShowingImageStatePanel() {
+        return mShowingImageStatePanel;
+    }
+
+    public void loadMainPanel() {
+        if (findViewById(R.id.main_panel_container) == null) {
+            return;
+        }
+        MainPanel panel = new MainPanel();
+        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+        transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
+        transaction.commit();
+    }
+
+    public void loadEditorPanel(FilterRepresentation representation,
+                                final Editor currentEditor) {
+        if (representation.getEditorId() == ImageOnlyEditor.ID) {
+            currentEditor.getImageShow().select();
+            currentEditor.reflectCurrentFilter();
+            return;
+        }
+        final int currentId = currentEditor.getID();
+        Runnable showEditor = new Runnable() {
+            @Override
+            public void run() {
+                EditorPanel panel = new EditorPanel();
+                panel.setEditor(currentId);
+                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+                transaction.remove(getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG));
+                transaction.replace(R.id.main_panel_container, panel, MainPanel.FRAGMENT_TAG);
+                transaction.commit();
+            }
+        };
+        Fragment main = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+        boolean doAnimation = false;
+        if (mShowingImageStatePanel
+                && getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+            doAnimation = true;
+        }
+        if (doAnimation && main != null && main instanceof MainPanel) {
+            MainPanel mainPanel = (MainPanel) main;
+            View container = mainPanel.getView().findViewById(R.id.category_panel_container);
+            View bottom = mainPanel.getView().findViewById(R.id.bottom_panel);
+            int panelHeight = container.getHeight() + bottom.getHeight();
+            mainPanel.getView().animate().translationY(panelHeight).withEndAction(showEditor).start();
+        } else {
+            showEditor.run();
+        }
+    }
+
+    private void loadXML() {
+        setContentView(R.layout.filtershow_activity);
+
+        ActionBar actionBar = getActionBar();
+        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+        actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+        mSaveButton = actionBar.getCustomView();
+        mSaveButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                saveImage();
+            }
+        });
+
+        mImageShow = (ImageShow) findViewById(R.id.imageShow);
+        mImageViews.add(mImageShow);
+
+        setupEditors();
+
+        mEditorPlaceHolder.hide();
+
+        mImageShow.setImageLoader(mImageLoader);
+
+        fillFx();
+        fillBorders();
+        fillGeometry();
+        fillFilters();
+
+        setupStatePanel();
+    }
+
+    public void setupStatePanel() {
+        mImageLoader.setAdapter(mMasterImage.getHistory());
+    }
+
+    private void fillFilters() {
+        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
+        FiltersManager filtersManager = FiltersManager.getManager();
+        filtersManager.addEffects(filtersRepresentations);
+
+        mCategoryFiltersAdapter = new CategoryAdapter(this);
+        for (FilterRepresentation representation : filtersRepresentations) {
+            if (representation.getTextId() != 0) {
+                representation.setName(getString(representation.getTextId()));
+            }
+            mCategoryFiltersAdapter.add(new Action(this, representation));
+        }
+    }
+
+    private void fillGeometry() {
+        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
+        FiltersManager filtersManager = FiltersManager.getManager();
+
+        GeometryMetadata geo = new GeometryMetadata();
+        int[] editorsId = geo.getEditorIds();
+        for (int i = 0; i < editorsId.length; i++) {
+            int editorId = editorsId[i];
+            GeometryMetadata geometry = new GeometryMetadata(geo);
+            geometry.setEditorId(editorId);
+            EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId);
+            geometry.setTextId(editorInfo.getTextId());
+            geometry.setOverlayId(editorInfo.getOverlayId());
+            geometry.setOverlayOnly(editorInfo.getOverlayOnly());
+            if (geometry.getTextId() != 0) {
+                geometry.setName(getString(geometry.getTextId()));
+            }
+            filtersRepresentations.add(geometry);
+        }
+
+        filtersManager.addTools(filtersRepresentations);
+
+        mCategoryGeometryAdapter = new CategoryAdapter(this);
+        for (FilterRepresentation representation : filtersRepresentations) {
+            mCategoryGeometryAdapter.add(new Action(this, representation));
+        }
+    }
+
+    private void processIntent() {
+        Intent intent = getIntent();
+        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+
+        mAction = intent.getAction();
+
+        if (intent.getData() != null) {
+            startLoadBitmap(intent.getData());
+        } else {
+            pickImage();
+        }
+    }
+
+    private void setupEditors() {
+        mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer));
+        EditorManager.addEditors(mEditorPlaceHolder);
+        mEditorPlaceHolder.setOldViews(mImageViews);
+        mEditorPlaceHolder.setImageLoader(mImageLoader);
+    }
+
+    private void fillEditors() {
+        mEditorPlaceHolder.addEditor(new EditorDraw());
+        mEditorPlaceHolder.addEditor(new BasicEditor());
+        mEditorPlaceHolder.addEditor(new ImageOnlyEditor());
+        mEditorPlaceHolder.addEditor(new EditorTinyPlanet());
+        mEditorPlaceHolder.addEditor(new EditorRedEye());
+        mEditorPlaceHolder.addEditor(new EditorCrop());
+        mEditorPlaceHolder.addEditor(new EditorFlip());
+        mEditorPlaceHolder.addEditor(new EditorRotate());
+        mEditorPlaceHolder.addEditor(new EditorStraighten());
+    }
+
+    private void setDefaultValues() {
+        ImageFilter.setActivityForMemoryToasts(this);
+
+        Resources res = getResources();
+        FiltersManager.setResources(res);
+
+        CategoryView.setMargin((int) getPixelsFromDip(8));
+        CategoryView.setTextSize((int) getPixelsFromDip(16));
+
+        ImageShow.setDefaultBackgroundColor(res.getColor(R.color.background_screen));
         // TODO: get those values from XML.
-        ImageZoom.setZoomedSize(getPixelsFromDip(256));
         FramedTextButton.setTextSize((int) getPixelsFromDip(14));
         FramedTextButton.setTrianglePadding((int) getPixelsFromDip(4));
         FramedTextButton.setTriangleSize((int) getPixelsFromDip(10));
@@ -165,323 +330,233 @@
         ImageShow.setTextPadding((int) getPixelsFromDip(10));
         ImageShow.setOriginalTextMargin((int) getPixelsFromDip(4));
         ImageShow.setOriginalTextSize((int) getPixelsFromDip(18));
-        ImageShow.setOriginalText(getResources().getString(R.string.original_picture_text));
-        ImageButtonTitle.setTextSize((int) getPixelsFromDip(12));
-        ImageButtonTitle.setTextPadding((int) getPixelsFromDip(10));
-        ImageSmallFilter.setMargin((int) getPixelsFromDip(3));
-        ImageSmallFilter.setTextMargin((int) getPixelsFromDip(4));
-        Drawable curveHandle = getResources().getDrawable(R.drawable.camera_crop);
-        int curveHandleSize = (int) getResources().getDimension(R.dimen.crop_indicator_size);
+        ImageShow.setOriginalText(res.getString(R.string.original_picture_text));
+
+        Drawable curveHandle = res.getDrawable(R.drawable.camera_crop);
+        int curveHandleSize = (int) res.getDimension(R.dimen.crop_indicator_size);
         Spline.setCurveHandle(curveHandle, curveHandleSize);
         Spline.setCurveWidth((int) getPixelsFromDip(3));
 
-        setContentView(R.layout.filtershow_activity);
-        ActionBar actionBar = getActionBar();
-        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
-        actionBar.setCustomView(R.layout.filtershow_actionbar);
-
-        actionBar.getCustomView().setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                saveImage();
-            }
-        });
-
-        mImageLoader = new ImageLoader(this, getApplicationContext());
-
-        LinearLayout listFilters = (LinearLayout) findViewById(R.id.listFilters);
-        LinearLayout listBorders = (LinearLayout) findViewById(R.id.listBorders);
-        LinearLayout listColors = (LinearLayout) findViewById(R.id.listColorsFx);
-
-        mImageShow = (ImageShow) findViewById(R.id.imageShow);
-        mImageCurves = (ImageCurves) findViewById(R.id.imageCurves);
-        mImageBorders = (ImageBorder) findViewById(R.id.imageBorder);
-        mImageStraighten = (ImageStraighten) findViewById(R.id.imageStraighten);
-        mImageZoom = (ImageZoom) findViewById(R.id.imageZoom);
-        mImageCrop = (ImageCrop) findViewById(R.id.imageCrop);
-        mImageRotate = (ImageRotate) findViewById(R.id.imageRotate);
-        mImageFlip = (ImageFlip) findViewById(R.id.imageFlip);
-        mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet);
-
-        mImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
+        ImageCrop.setAspectTextSize((int) getPixelsFromDip(18));
         ImageCrop.setTouchTolerance((int) getPixelsFromDip(25));
-        mImageViews.add(mImageShow);
-        mImageViews.add(mImageCurves);
-        mImageViews.add(mImageBorders);
-        mImageViews.add(mImageStraighten);
-        mImageViews.add(mImageZoom);
-        mImageViews.add(mImageCrop);
-        mImageViews.add(mImageRotate);
-        mImageViews.add(mImageFlip);
-        mImageViews.add(mImageTinyPlanet);
-
-        mListFx = findViewById(R.id.fxList);
-        mListBorders = findViewById(R.id.bordersList);
-        mListGeometry = findViewById(R.id.geometryList);
-        mListFilterButtons = findViewById(R.id.filterButtonsList);
-        mListColors = findViewById(R.id.colorsFxList);
-        mListViews.add(mListFx);
-        mListViews.add(mListBorders);
-        mListViews.add(mListGeometry);
-        mListViews.add(mListFilterButtons);
-        mListViews.add(mListColors);
-
-        mFxButton = (ImageButton) findViewById(R.id.fxButton);
-        mBorderButton = (ImageButton) findViewById(R.id.borderButton);
-        mGeometryButton = (ImageButton) findViewById(R.id.geometryButton);
-        mColorsButton = (ImageButton) findViewById(R.id.colorsButton);
-
-        mBottomPanelButtons.add(mFxButton);
-        mBottomPanelButtons.add(mBorderButton);
-        mBottomPanelButtons.add(mGeometryButton);
-        mBottomPanelButtons.add(mColorsButton);
-
-        mImageShow.setImageLoader(mImageLoader);
-        mImageCurves.setImageLoader(mImageLoader);
-        mImageCurves.setMaster(mImageShow);
-        mImageBorders.setImageLoader(mImageLoader);
-        mImageBorders.setMaster(mImageShow);
-        mImageStraighten.setImageLoader(mImageLoader);
-        mImageStraighten.setMaster(mImageShow);
-        mImageZoom.setImageLoader(mImageLoader);
-        mImageZoom.setMaster(mImageShow);
-        mImageCrop.setImageLoader(mImageLoader);
-        mImageCrop.setMaster(mImageShow);
-        mImageRotate.setImageLoader(mImageLoader);
-        mImageRotate.setMaster(mImageShow);
-        mImageFlip.setImageLoader(mImageLoader);
-        mImageFlip.setMaster(mImageShow);
-        mImageTinyPlanet.setImageLoader(mImageLoader);
-        mImageTinyPlanet.setMaster(mImageShow);
-
-        mPanelController.setActivity(this);
-
-        mPanelController.addImageView(findViewById(R.id.imageShow));
-        mPanelController.addImageView(findViewById(R.id.imageCurves));
-        mPanelController.addImageView(findViewById(R.id.imageBorder));
-        mPanelController.addImageView(findViewById(R.id.imageStraighten));
-        mPanelController.addImageView(findViewById(R.id.imageCrop));
-        mPanelController.addImageView(findViewById(R.id.imageRotate));
-        mPanelController.addImageView(findViewById(R.id.imageFlip));
-        mPanelController.addImageView(findViewById(R.id.imageZoom));
-        mPanelController.addImageView(findViewById(R.id.imageTinyPlanet));
-
-        mPanelController.addPanel(mFxButton, mListFx, 0);
-        mPanelController.addPanel(mBorderButton, mListBorders, 1);
-
-        mPanelController.addPanel(mGeometryButton, mListGeometry, 2);
-        mPanelController.addComponent(mGeometryButton, findViewById(R.id.straightenButton));
-        mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton));
-        mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton));
-        mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton));
-
-        mPanelController.addPanel(mColorsButton, mListColors, 3);
-
-        int[] recastIDs = {
-                R.id.tinyplanetButton,
-                R.id.vignetteButton,
-                R.id.vibranceButton,
-                R.id.contrastButton,
-                R.id.saturationButton,
-                R.id.bwfilterButton,
-                R.id.wbalanceButton,
-                R.id.hueButton,
-                R.id.exposureButton,
-                R.id.shadowRecoveryButton
-        };
-        ImageFilter[] filters = {
-                new ImageFilterTinyPlanet(),
-                new ImageFilterVignette(),
-                new ImageFilterVibrance(),
-                new ImageFilterContrast(),
-                new ImageFilterSaturated(),
-                new ImageFilterBwFilter(),
-                new ImageFilterWBalance(),
-                new ImageFilterHue(),
-                new ImageFilterExposure(),
-                new ImageFilterShadows()
-        };
-
-        for (int i = 0; i < filters.length; i++) {
-            ImageSmallFilter fView = new ImageSmallFilter(this);
-            View v = listColors.findViewById(recastIDs[i]);
-            int pos = listColors.indexOfChild(v);
-            listColors.removeView(v);
-
-            filters[i].setParameter(filters[i].getPreviewParameter());
-            if (v instanceof ImageButtonTitle)
-                filters[i].setName(((ImageButtonTitle) v).getText());
-            fView.setImageFilter(filters[i]);
-            fView.setController(this);
-            fView.setImageLoader(mImageLoader);
-            fView.setId(recastIDs[i]);
-            mPanelController.addComponent(mColorsButton, fView);
-            listColors.addView(fView, pos);
-        }
-
-        int[] overlayIDs = {
-                R.id.sharpenButton,
-                R.id.curvesButtonRGB
-        };
-        int[] overlayBitmaps = {
-                R.drawable.filtershow_button_colors_sharpen,
-                R.drawable.filtershow_button_colors_curve
-        };
-        int[] overlayNames = {
-                R.string.sharpness,
-                R.string.curvesRGB
-        };
-
-        for (int i = 0; i < overlayIDs.length; i++) {
-            ImageWithIcon fView = new ImageWithIcon(this);
-            View v = listColors.findViewById(overlayIDs[i]);
-            int pos = listColors.indexOfChild(v);
-            listColors.removeView(v);
-            final int sid = overlayNames[i];
-            ImageFilterExposure efilter = new ImageFilterExposure() {
-                {
-                    mName = getString(sid);
-                }
-            };
-            efilter.setParameter(-300);
-            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),
-                    overlayBitmaps[i]);
-
-            fView.setIcon(bitmap);
-            fView.setImageFilter(efilter);
-            fView.setController(this);
-            fView.setImageLoader(mImageLoader);
-            fView.setId(overlayIDs[i]);
-            mPanelController.addComponent(mColorsButton, fView);
-            listColors.addView(fView, pos);
-        }
-
-        mPanelController.addView(findViewById(R.id.applyEffect));
-        mPanelController.addView(findViewById(R.id.pickCurvesChannel));
-        mPanelController.addView(findViewById(R.id.aspect));
-        findViewById(R.id.resetOperationsButton).setOnClickListener(
-                createOnClickResetOperationsButton());
-
-        ListView operationsList = (ListView) findViewById(R.id.operationsList);
-        operationsList.setAdapter(mImageShow.getHistory());
-        operationsList.setOnItemClickListener(this);
-        ListView imageStateList = (ListView) findViewById(R.id.imageStateList);
-        imageStateList.setAdapter(mImageShow.getImageStateAdapter());
-        mImageLoader.setAdapter(mImageShow.getHistory());
-
-        fillListImages(listFilters);
-        fillListBorders(listBorders);
-
-        SeekBar seekBar = (SeekBar) findViewById(R.id.filterSeekBar);
-        seekBar.setMax(SEEK_BAR_MAX);
-
-        mImageShow.setSeekBar(seekBar);
-        mImageZoom.setSeekBar(seekBar);
-        mImageTinyPlanet.setSeekBar(seekBar);
-        mPanelController.setRowPanel(findViewById(R.id.secondRowPanel));
-        mPanelController.setUtilityPanel(this, findViewById(R.id.filterButtonsList),
-                findViewById(R.id.applyEffect), findViewById(R.id.aspect),
-                findViewById(R.id.pickCurvesChannel));
-        mPanelController.setMasterImage(mImageShow);
-        mPanelController.setCurrentPanel(mFxButton);
-        Intent intent = getIntent();
-        if (intent.getBooleanExtra(LAUNCH_FULLSCREEN, false)) {
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
-        }
-
-        if (intent.getData() != null) {
-            startLoadBitmap(intent.getData());
-        } else {
-            pickImage();
-        }
-
-        String action = intent.getAction();
-        if (action.equalsIgnoreCase(CROP_ACTION)) {
-            mPanelController.showComponent(findViewById(R.id.cropButton));
-        } else if (action.equalsIgnoreCase(TINY_PLANET_ACTION)) {
-            mPanelController.showComponent(findViewById(R.id.tinyplanetButton));
-        }
+        ImageCrop.setMinCropSize((int) getPixelsFromDip(55));
     }
 
     private void startLoadBitmap(Uri uri) {
-        final View filters = findViewById(R.id.filtersPanel);
+        mLoading = true;
         final View loading = findViewById(R.id.loading);
+        final View imageShow = findViewById(R.id.imageShow);
+        imageShow.setVisibility(View.INVISIBLE);
         loading.setVisibility(View.VISIBLE);
-        filters.setVisibility(View.INVISIBLE);
-        View tinyPlanetView = findViewById(R.id.tinyplanetButton);
-        if (tinyPlanetView != null) {
-            tinyPlanetView.setVisibility(View.GONE);
-        }
-        mLoadBitmapTask = new LoadBitmapTask(tinyPlanetView);
+        mShowingTinyPlanet = false;
+        mLoadBitmapTask = new LoadBitmapTask();
         mLoadBitmapTask.execute(uri);
     }
 
-    private class LoadBitmapTask extends AsyncTask<Uri, Void, Boolean> {
-        View mTinyPlanetButton;
+    private void fillBorders() {
+        Vector<FilterRepresentation> borders = new Vector<FilterRepresentation>();
+
+        // The "no border" implementation
+        borders.add(new FilterImageBorderRepresentation(0));
+
+        // Google-build borders
+        FiltersManager.getManager().addBorders(this, borders);
+
+        for (int i = 0; i < borders.size(); i++) {
+            FilterRepresentation filter = borders.elementAt(i);
+            if (i == 0) {
+                filter.setName(getString(R.string.none));
+            }
+        }
+
+        mCategoryBordersAdapter = new CategoryAdapter(this);
+        for (FilterRepresentation representation : borders) {
+            if (representation.getTextId() != 0) {
+                representation.setName(getString(representation.getTextId()));
+            }
+            mCategoryBordersAdapter.add(new Action(this, representation, Action.FULL_VIEW));
+        }
+    }
+
+    public CategoryAdapter getCategoryLooksAdapter() {
+        return mCategoryLooksAdapter;
+    }
+
+    public CategoryAdapter getCategoryBordersAdapter() {
+        return mCategoryBordersAdapter;
+    }
+
+    public CategoryAdapter getCategoryGeometryAdapter() {
+        return mCategoryGeometryAdapter;
+    }
+
+    public CategoryAdapter getCategoryFiltersAdapter() {
+        return mCategoryFiltersAdapter;
+    }
+
+    public void removeFilterRepresentation(FilterRepresentation filterRepresentation) {
+        if (filterRepresentation == null) {
+            return;
+        }
+        ImagePreset oldPreset = MasterImage.getImage().getPreset();
+        ImagePreset copy = new ImagePreset(oldPreset);
+        copy.removeFilter(filterRepresentation);
+        MasterImage.getImage().setPreset(copy, true);
+        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
+            FilterRepresentation lastRepresentation = copy.getLastRepresentation();
+            MasterImage.getImage().setCurrentFilterRepresentation(lastRepresentation);
+        }
+    }
+
+    public void useFilterRepresentation(FilterRepresentation filterRepresentation) {
+        if (filterRepresentation == null) {
+            return;
+        }
+        if (MasterImage.getImage().getCurrentFilterRepresentation() == filterRepresentation) {
+            return;
+        }
+        ImagePreset oldPreset = MasterImage.getImage().getPreset();
+        ImagePreset copy = new ImagePreset(oldPreset);
+        FilterRepresentation representation = copy.getRepresentation(filterRepresentation);
+        if (representation == null) {
+            copy.addFilter(filterRepresentation);
+        } else {
+            if (filterRepresentation.allowsMultipleInstances()) {
+                representation.updateTempParametersFrom(filterRepresentation);
+                copy.setHistoryName(filterRepresentation.getName());
+                representation.synchronizeRepresentation();
+            }
+            filterRepresentation = representation;
+        }
+        MasterImage.getImage().setPreset(copy, true);
+        MasterImage.getImage().setCurrentFilterRepresentation(filterRepresentation);
+    }
+
+    public void showRepresentation(FilterRepresentation representation) {
+        if (representation == null) {
+            return;
+        }
+        useFilterRepresentation(representation);
+
+        // show representation
+        Editor mCurrentEditor = mEditorPlaceHolder.showEditor(representation.getEditorId());
+        loadEditorPanel(representation, mCurrentEditor);
+    }
+
+    public Editor getEditor(int editorID) {
+        return mEditorPlaceHolder.getEditor(editorID);
+    }
+
+    public void setCurrentPanel(int currentPanel) {
+        mCurrentPanel = currentPanel;
+    }
+
+    public int getCurrentPanel() {
+        return mCurrentPanel;
+    }
+
+    private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> {
         int mBitmapSize;
 
-        public LoadBitmapTask(View button) {
-            mTinyPlanetButton = button;
+        public LoadBitmapTask() {
             mBitmapSize = getScreenImageSize();
         }
 
         @Override
         protected Boolean doInBackground(Uri... params) {
-            mImageLoader.loadBitmap(params[0], mBitmapSize);
-            publishProgress();
-            return mImageLoader.queryLightCycle360();
+            if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) {
+                return false;
+            }
+            publishProgress(mImageLoader.queryLightCycle360());
+            return true;
         }
 
         @Override
-        protected void onProgressUpdate(Void... values) {
+        protected void onProgressUpdate(Boolean... values) {
             super.onProgressUpdate(values);
-            if (isCancelled()) return;
-            final View filters = findViewById(R.id.filtersPanel);
-            final View loading = findViewById(R.id.loading);
-            loading.setVisibility(View.GONE);
-            filters.setVisibility(View.VISIBLE);
+            if (isCancelled()) {
+                return;
+            }
+            if (values[0]) {
+                mShowingTinyPlanet = true;
+            }
         }
 
         @Override
         protected void onPostExecute(Boolean result) {
+            MasterImage.setMaster(mMasterImage);
             if (isCancelled()) {
                 return;
             }
-            if (result) {
-                mTinyPlanetButton.setVisibility(View.VISIBLE);
+
+            if (!result) {
+                cannotLoadImage();
             }
+
+            final View loading = findViewById(R.id.loading);
+            loading.setVisibility(View.GONE);
+            final View imageShow = findViewById(R.id.imageShow);
+            imageShow.setVisibility(View.VISIBLE);
+
+            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
+            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
+            pipeline.setOriginal(largeBitmap);
+            float previewScale = (float) largeBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
+            pipeline.setPreviewScaleFactor(previewScale);
+            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
+            if (highresBitmap != null) {
+                float highResPreviewScale = (float) highresBitmap.getWidth() / (float) mImageLoader.getOriginalBounds().width();
+                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
+            }
+            if (!mShowingTinyPlanet) {
+                mCategoryFiltersAdapter.removeTinyPlanet();
+            }
+            pipeline.turnOnPipeline(true);
+            MasterImage.getImage().setOriginalGeometry(largeBitmap);
+            mCategoryLooksAdapter.imageLoaded();
+            mCategoryBordersAdapter.imageLoaded();
+            mCategoryGeometryAdapter.imageLoaded();
+            mCategoryFiltersAdapter.imageLoaded();
             mLoadBitmapTask = null;
+
+            if (mAction == TINY_PLANET_ACTION) {
+                showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
+            }
+            mLoading = false;
             super.onPostExecute(result);
         }
 
     }
 
+    private void clearGalleryBitmapPool() {
+        (new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... params) {
+                // Free memory held in Gallery's Bitmap pool.  May be O(n) for n bitmaps.
+                GalleryBitmapPool.getInstance().clear();
+                return null;
+            }
+        }).execute();
+    }
+
     @Override
     protected void onDestroy() {
         if (mLoadBitmapTask != null) {
             mLoadBitmapTask.cancel(false);
         }
+        // TODO:  refactor, don't use so many singletons.
+        FilteringPipeline.getPipeline().turnOnPipeline(false);
+        MasterImage.reset();
+        FilteringPipeline.reset();
+        ImageFilter.resetStatics();
+        FiltersManager.getPreviewManager().freeRSFilterScripts();
+        FiltersManager.getManager().freeRSFilterScripts();
+        FiltersManager.getHighresManager().freeRSFilterScripts();
+        FiltersManager.reset();
+        CachingPipeline.destroyRenderScriptContext();
         super.onDestroy();
     }
 
-    private int translateMainPanel(View viewPanel) {
-        int accessoryPanelWidth = viewPanel.getWidth();
-        int mainViewWidth = findViewById(R.id.mainView).getWidth();
-        int mainPanelWidth = mImageShow.getDisplayedImageBounds().width();
-        if (mainPanelWidth == 0) {
-            mainPanelWidth = mainViewWidth;
-        }
-        int filtersPanelWidth = findViewById(R.id.filtersPanel).getWidth();
-        if (mainPanelWidth < filtersPanelWidth) {
-            mainPanelWidth = filtersPanelWidth;
-        }
-        int leftOver = mainViewWidth - mainPanelWidth - accessoryPanelWidth;
-        if (leftOver < 0) {
-            return -accessoryPanelWidth;
-        }
-        return 0;
-    }
-
     private int getScreenImageSize() {
         DisplayMetrics metrics = new DisplayMetrics();
         Display display = getWindowManager().getDefaultDisplay();
@@ -566,12 +641,6 @@
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.filtershow_activity_menu, menu);
-        MenuItem showHistory = menu.findItem(R.id.operationsButton);
-        if (mShowingHistoryPanel) {
-            showHistory.setTitle(R.string.hide_history_panel);
-        } else {
-            showHistory.setTitle(R.string.show_history_panel);
-        }
         MenuItem showState = menu.findItem(R.id.showImageStateButton);
         if (mShowingImageStatePanel) {
             showState.setTitle(R.string.hide_imagestate_panel);
@@ -586,13 +655,14 @@
         MenuItem undoItem = menu.findItem(R.id.undoButton);
         MenuItem redoItem = menu.findItem(R.id.redoButton);
         MenuItem resetItem = menu.findItem(R.id.resetHistoryButton);
-        mImageShow.getHistory().setMenuItems(undoItem, redoItem, resetItem);
+        mMasterImage.getHistory().setMenuItems(undoItem, redoItem, resetItem);
         return true;
     }
 
     @Override
     public void onPause() {
         super.onPause();
+        rsPause();
         if (mShareActionProvider != null) {
             mShareActionProvider.setOnShareTargetSelectedListener(null);
         }
@@ -601,26 +671,64 @@
     @Override
     public void onResume() {
         super.onResume();
+        rsResume();
         if (mShareActionProvider != null) {
             mShareActionProvider.setOnShareTargetSelectedListener(this);
         }
     }
 
+    private void rsResume() {
+        ImageFilter.setActivityForMemoryToasts(this);
+        MasterImage.setMaster(mMasterImage);
+        if (CachingPipeline.getRenderScriptContext() == null) {
+            CachingPipeline.createRenderscriptContext(this);
+        }
+        FiltersManager.setResources(getResources());
+        if (!mLoading) {
+            Bitmap largeBitmap = mImageLoader.getOriginalBitmapLarge();
+            FilteringPipeline pipeline = FilteringPipeline.getPipeline();
+            pipeline.setOriginal(largeBitmap);
+            float previewScale = (float) largeBitmap.getWidth() /
+                    (float) mImageLoader.getOriginalBounds().width();
+            pipeline.setPreviewScaleFactor(previewScale);
+            Bitmap highresBitmap = mImageLoader.getOriginalBitmapHighres();
+            if (highresBitmap != null) {
+                float highResPreviewScale = (float) highresBitmap.getWidth() /
+                        (float) mImageLoader.getOriginalBounds().width();
+                pipeline.setHighResPreviewScaleFactor(highResPreviewScale);
+            }
+            pipeline.turnOnPipeline(true);
+            MasterImage.getImage().setOriginalGeometry(largeBitmap);
+        }
+    }
+
+    private void rsPause() {
+        FilteringPipeline.getPipeline().turnOnPipeline(false);
+        FilteringPipeline.reset();
+        ImageFilter.resetStatics();
+        FiltersManager.getPreviewManager().freeRSFilterScripts();
+        FiltersManager.getManager().freeRSFilterScripts();
+        FiltersManager.getHighresManager().freeRSFilterScripts();
+        FiltersManager.reset();
+        CachingPipeline.destroyRenderScriptContext();
+    }
+
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.undoButton: {
-                HistoryAdapter adapter = mImageShow.getHistory();
+                HistoryAdapter adapter = mMasterImage.getHistory();
                 int position = adapter.undo();
-                mImageShow.onItemClick(position);
+                mMasterImage.onHistoryItemClick(position);
                 mImageShow.showToast("Undo");
+                backToMain();
                 invalidateViews();
                 return true;
             }
             case R.id.redoButton: {
-                HistoryAdapter adapter = mImageShow.getHistory();
+                HistoryAdapter adapter = mMasterImage.getHistory();
                 int position = adapter.redo();
-                mImageShow.onItemClick(position);
+                mMasterImage.onHistoryItemClick(position);
                 mImageShow.showToast("Redo");
                 invalidateViews();
                 return true;
@@ -633,10 +741,6 @@
                 toggleImageStatePanel();
                 return true;
             }
-            case R.id.operationsButton: {
-                toggleHistoryPanel();
-                return true;
-            }
             case android.R.id.home: {
                 saveImage();
                 return true;
@@ -645,130 +749,38 @@
         return false;
     }
 
-    private void fillListImages(LinearLayout listFilters) {
-        // TODO: use listview
-        // TODO: load the filters straight from the filesystem
-
-        ImageFilterFx[] fxArray = new ImageFilterFx[18];
-        int p = 0;
-
-        int[] drawid = {
-                R.drawable.filtershow_fx_0005_punch,
-                R.drawable.filtershow_fx_0000_vintage,
-                R.drawable.filtershow_fx_0004_bw_contrast,
-                R.drawable.filtershow_fx_0002_bleach,
-                R.drawable.filtershow_fx_0001_instant,
-                R.drawable.filtershow_fx_0007_washout,
-                R.drawable.filtershow_fx_0003_blue_crush,
-                R.drawable.filtershow_fx_0008_washout_color,
-                R.drawable.filtershow_fx_0006_x_process
-        };
-
-        int[] fxNameid = {
-                R.string.ffx_punch,
-                R.string.ffx_vintage,
-                R.string.ffx_bw_contrast,
-                R.string.ffx_bleach,
-                R.string.ffx_instant,
-                R.string.ffx_washout,
-                R.string.ffx_blue_crush,
-                R.string.ffx_washout_color,
-                R.string.ffx_x_process
-        };
-
-        ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
-        preset.setImageLoader(mImageLoader);
-        mNullFxFilter = new ImageSmallFilter(this);
-
-        mNullFxFilter.setSelected(true);
-        mCurrentImageSmallFilter = mNullFxFilter;
-
-        mNullFxFilter.setImageFilter(new ImageFilterFx(null, getString(R.string.none)));
-
-        mNullFxFilter.setController(this);
-        mNullFxFilter.setImageLoader(mImageLoader);
-        listFilters.addView(mNullFxFilter);
-        ImageSmallFilter previousFilter = mNullFxFilter;
-
-        BitmapFactory.Options o = new BitmapFactory.Options();
-        o.inScaled = false;
-
-        for (int i = 0; i < drawid.length; i++) {
-            Bitmap b = BitmapFactory.decodeResource(getResources(), drawid[i], o);
-            fxArray[p++] = new ImageFilterFx(b, getString(fxNameid[i]));
-        }
-        ImageSmallFilter filter;
-        for (int i = 0; i < p; i++) {
-            filter = new ImageSmallFilter(this);
-            filter.setImageFilter(fxArray[i]);
-            filter.setController(this);
-            filter.setNulfilter(mNullFxFilter);
-            filter.setImageLoader(mImageLoader);
-            listFilters.addView(filter);
-            previousFilter = filter;
-        }
-
-        // Default preset (original)
-        mImageShow.setImagePreset(preset);
+    public void enableSave(boolean enable) {
+        if (mSaveButton != null)
+            mSaveButton.setEnabled(enable);
     }
 
-    private void fillListBorders(LinearLayout listBorders) {
-        // TODO: use listview
-        // TODO: load the borders straight from the filesystem
-        int p = 0;
-        ImageFilter[] borders = new ImageFilter[12];
-        borders[p++] = new ImageFilterBorder(null);
+    private void fillFx() {
+        FilterFxRepresentation nullFx =
+                new FilterFxRepresentation(getString(R.string.none), 0, R.string.none);
+        Vector<FilterRepresentation> filtersRepresentations = new Vector<FilterRepresentation>();
+        FiltersManager.getManager().addLooks(this, filtersRepresentations);
 
-        Drawable npd1 = getResources().getDrawable(R.drawable.filtershow_border_4x5);
-        borders[p++] = new ImageFilterBorder(npd1);
-        Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_brush);
-        borders[p++] = new ImageFilterBorder(npd2);
-        Drawable npd3 = getResources().getDrawable(R.drawable.filtershow_border_grunge);
-        borders[p++] = new ImageFilterBorder(npd3);
-        Drawable npd4 = getResources().getDrawable(R.drawable.filtershow_border_sumi_e);
-        borders[p++] = new ImageFilterBorder(npd4);
-        Drawable npd5 = getResources().getDrawable(R.drawable.filtershow_border_tape);
-        borders[p++] = new ImageFilterBorder(npd5);
-        borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize, 0);
-        borders[p++] = new ImageFilterParametricBorder(Color.BLACK, mImageBorderSize,
-                mImageBorderSize);
-        borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize, 0);
-        borders[p++] = new ImageFilterParametricBorder(Color.WHITE, mImageBorderSize,
-                mImageBorderSize);
-        int creamColor = Color.argb(255, 237, 237, 227);
-        borders[p++] = new ImageFilterParametricBorder(creamColor, mImageBorderSize, 0);
-        borders[p++] = new ImageFilterParametricBorder(creamColor, mImageBorderSize,
-                mImageBorderSize);
-
-        ImageSmallFilter previousFilter = null;
-        for (int i = 0; i < p; i++) {
-            ImageSmallBorder filter = new ImageSmallBorder(this);
-            if (i == 0) { // save the first to reset it
-                mNullBorderFilter = filter;
-            } else {
-                filter.setNulfilter(mNullBorderFilter);
-            }
-            borders[i].setName(getString(R.string.borders));
-            filter.setImageFilter(borders[i]);
-            filter.setController(this);
-            filter.setBorder(true);
-            filter.setImageLoader(mImageLoader);
-            filter.setShowTitle(false);
-            listBorders.addView(filter);
-            previousFilter = filter;
+        mCategoryLooksAdapter = new CategoryAdapter(this);
+        int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height);
+        mCategoryLooksAdapter.setItemHeight(verticalItemHeight);
+        mCategoryLooksAdapter.add(new Action(this, nullFx, Action.FULL_VIEW));
+        for (FilterRepresentation representation : filtersRepresentations) {
+            mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW));
         }
     }
 
+    public void setDefaultPreset() {
+        // Default preset (original)
+        ImagePreset preset = new ImagePreset(getString(R.string.history_original)); // empty
+        preset.setImageLoader(mImageLoader);
+
+        mMasterImage.setPreset(preset, true);
+    }
+
     // //////////////////////////////////////////////////////////////////////////////
     // Some utility functions
     // TODO: finish the cleanup.
 
-    public void showOriginalViews(boolean value) {
-        for (ImageShow views : mImageViews) {
-            views.showOriginal(value);
-        }
-    }
-
     public void invalidateViews() {
         for (ImageShow views : mImageViews) {
             views.invalidate();
@@ -776,155 +788,117 @@
         }
     }
 
-    public void hideListViews() {
-        for (View view : mListViews) {
-            view.setVisibility(View.GONE);
-        }
-    }
-
     public void hideImageViews() {
-        mImageShow.setShowControls(false); // reset
         for (View view : mImageViews) {
             view.setVisibility(View.GONE);
         }
-    }
-
-    public void unselectBottomPanelButtons() {
-        for (ImageButton button : mBottomPanelButtons) {
-            button.setSelected(false);
-        }
-    }
-
-    public void unselectPanelButtons(Vector<ImageButton> buttons) {
-        for (ImageButton button : buttons) {
-            button.setSelected(false);
-        }
-    }
-
-    public void disableFilterButtons() {
-        for (ImageButton b : mBottomPanelButtons) {
-            b.setEnabled(false);
-            b.setClickable(false);
-            b.setAlpha(0.4f);
-        }
-    }
-
-    public void enableFilterButtons() {
-        for (ImageButton b : mBottomPanelButtons) {
-            b.setEnabled(true);
-            b.setClickable(true);
-            b.setAlpha(1.0f);
-        }
+        mEditorPlaceHolder.hide();
     }
 
     // //////////////////////////////////////////////////////////////////////////////
     // imageState panel...
 
-    public boolean isShowingHistoryPanel() {
-        return mShowingHistoryPanel;
-    }
-
-    private void toggleImageStatePanel() {
-        final View view = findViewById(R.id.mainPanel);
-        final View viewList = findViewById(R.id.imageStatePanel);
-
-        if (mShowingHistoryPanel) {
-            findViewById(R.id.historyPanel).setVisibility(View.INVISIBLE);
-            mShowingHistoryPanel = false;
-        }
-
-        int translate = translateMainPanel(viewList);
-        if (!mShowingImageStatePanel) {
-            mShowingImageStatePanel = true;
-            view.animate().setDuration(200).x(translate)
-                    .withLayer().withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            viewList.setAlpha(0);
-                            viewList.setVisibility(View.VISIBLE);
-                            viewList.animate().setDuration(100)
-                                    .alpha(1.0f).start();
-                        }
-                    }).start();
-        } else {
-            mShowingImageStatePanel = false;
-            viewList.setVisibility(View.INVISIBLE);
-            view.animate().setDuration(200).x(0).withLayer()
-                    .start();
-        }
+    public void toggleImageStatePanel() {
         invalidateOptionsMenu();
+        mShowingImageStatePanel = !mShowingImageStatePanel;
+        Fragment panel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+        if (panel != null) {
+            if (panel instanceof EditorPanel) {
+                EditorPanel editorPanel = (EditorPanel) panel;
+                editorPanel.showImageStatePanel(mShowingImageStatePanel);
+            } else if (panel instanceof MainPanel) {
+                MainPanel mainPanel = (MainPanel) panel;
+                mainPanel.showImageStatePanel(mShowingImageStatePanel);
+            }
+        }
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig)
     {
         super.onConfigurationChanged(newConfig);
-        if (mShowingHistoryPanel) {
-            toggleHistoryPanel();
+        setDefaultValues();
+        loadXML();
+        loadMainPanel();
+
+        // mLoadBitmapTask==null implies you have looked at the intent
+        if (!mShowingTinyPlanet && (mLoadBitmapTask == null)) {
+            mCategoryFiltersAdapter.removeTinyPlanet();
         }
+        final View loading = findViewById(R.id.loading);
+        loading.setVisibility(View.GONE);
     }
 
-    // //////////////////////////////////////////////////////////////////////////////
-    // history panel...
+    public void setupMasterImage() {
+        mImageLoader = new ImageLoader(this, getApplicationContext());
 
-    public void toggleHistoryPanel() {
-        final View view = findViewById(R.id.mainPanel);
-        final View viewList = findViewById(R.id.historyPanel);
+        HistoryAdapter mHistoryAdapter = new HistoryAdapter(
+                this, R.layout.filtershow_history_operation_row,
+                R.id.rowTextView);
 
-        if (mShowingImageStatePanel) {
-            findViewById(R.id.imageStatePanel).setVisibility(View.INVISIBLE);
-            mShowingImageStatePanel = false;
-        }
+        StateAdapter mImageStateAdapter = new StateAdapter(this, 0);
+        MasterImage.reset();
+        mMasterImage = MasterImage.getImage();
+        mMasterImage.setHistoryAdapter(mHistoryAdapter);
+        mMasterImage.setStateAdapter(mImageStateAdapter);
+        mMasterImage.setActivity(this);
+        mMasterImage.setImageLoader(mImageLoader);
 
-        int translate = translateMainPanel(viewList);
-        if (!mShowingHistoryPanel) {
-            mShowingHistoryPanel = true;
-            view.animate().setDuration(200).x(translate)
-                    .withLayer().withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            viewList.setAlpha(0);
-                            viewList.setVisibility(View.VISIBLE);
-                            viewList.animate().setDuration(100)
-                                    .alpha(1.0f).start();
-                        }
-                    }).start();
+        if (Runtime.getRuntime().maxMemory() > LIMIT_SUPPORTS_HIGHRES) {
+            mMasterImage.setSupportsHighRes(true);
         } else {
-            mShowingHistoryPanel = false;
-            viewList.setVisibility(View.INVISIBLE);
-            view.animate().setDuration(200).x(0).withLayer()
-                    .start();
+            mMasterImage.setSupportsHighRes(false);
         }
-        invalidateOptionsMenu();
     }
 
     void resetHistory() {
-        mNullFxFilter.onClick(mNullFxFilter);
-        mNullBorderFilter.onClick(mNullBorderFilter);
-
-        HistoryAdapter adapter = mImageShow.getHistory();
+        HistoryAdapter adapter = mMasterImage.getHistory();
         adapter.reset();
         ImagePreset original = new ImagePreset(adapter.getItem(0));
-        mImageShow.setImagePreset(original);
-        mPanelController.resetParameters();
+        mMasterImage.setPreset(original, true);
         invalidateViews();
+        backToMain();
     }
 
-    // reset button in the history panel.
-    private OnClickListener createOnClickResetOperationsButton() {
-        return new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
+    public void showDefaultImageView() {
+        mEditorPlaceHolder.hide();
+        mImageShow.setVisibility(View.VISIBLE);
+        MasterImage.getImage().setCurrentFilter(null);
+        MasterImage.getImage().setCurrentFilterRepresentation(null);
+    }
 
-                resetHistory();
-            }
-        };
+    public void backToMain() {
+        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+        if (currentPanel instanceof MainPanel) {
+            return;
+        }
+        loadMainPanel();
+        showDefaultImageView();
     }
 
     @Override
     public void onBackPressed() {
-        if (mPanelController.onBackPressed()) {
-            saveImage();
+        Fragment currentPanel = getSupportFragmentManager().findFragmentByTag(MainPanel.FRAGMENT_TAG);
+        if (currentPanel instanceof MainPanel) {
+            if (!mImageShow.hasModifications()) {
+                done();
+            } else {
+                AlertDialog.Builder builder = new AlertDialog.Builder(this);
+                builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit);
+                builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        saveImage();
+                    }
+                });
+                builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        done();
+                    }
+                });
+                builder.show();
+            }
+        } else {
+            backToMain();
         }
     }
 
@@ -943,52 +917,10 @@
                 r.getDisplayMetrics());
     }
 
-    public void useImagePreset(ImageSmallFilter imageSmallFilter, ImagePreset preset) {
-        if (preset == null) {
-            return;
-        }
-
-        if (mCurrentImageSmallFilter != null) {
-            mCurrentImageSmallFilter.setSelected(false);
-        }
-        mCurrentImageSmallFilter = imageSmallFilter;
-        mCurrentImageSmallFilter.setSelected(true);
-
-        ImagePreset copy = new ImagePreset(preset);
-        mImageShow.setImagePreset(copy);
-        if (preset.isFx()) {
-            // if it's an FX we rest the curve adjustment too
-            mImageCurves.resetCurve();
-        }
-        invalidateViews();
-    }
-
-    public void useImageFilter(ImageSmallFilter imageSmallFilter, ImageFilter imageFilter,
-            boolean setBorder) {
-        if (imageFilter == null) {
-            return;
-        }
-
-        if (mCurrentImageSmallFilter != null) {
-            mCurrentImageSmallFilter.setSelected(false);
-        }
-        mCurrentImageSmallFilter = imageSmallFilter;
-        mCurrentImageSmallFilter.setSelected(true);
-
-        ImagePreset oldPreset = mImageShow.getImagePreset();
-        ImagePreset copy = new ImagePreset(oldPreset);
-        // TODO: use a numerical constant instead.
-
-        copy.add(imageFilter);
-
-        mImageShow.setImagePreset(copy);
-        invalidateViews();
-    }
-
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position,
             long id) {
-        mImageShow.onItemClick(position);
+        mMasterImage.onHistoryItemClick(position);
         invalidateViews();
     }
 
@@ -1002,7 +934,6 @@
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Log.v(LOGTAG, "onActivityResult");
         if (resultCode == RESULT_OK) {
             if (requestCode == SELECT_PICTURE) {
                 Uri selectedImageUri = data.getData();
@@ -1011,19 +942,114 @@
         }
     }
 
+    private boolean mSaveToExtraUri = false;
+    private boolean mSaveAsWallpaper = false;
+    private boolean mReturnAsExtra = false;
+    private boolean mOutputted = false;
+
     public void saveImage() {
-        if (mImageShow.hasModifications()) {
-            // Get the name of the album, to which the image will be saved
-            File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri());
-            int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
-            String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
-            showSavingProgress(albumName);
-            mImageShow.saveImage(this, null);
-        } else {
-            finish();
+        handleSpecialExitCases();
+        if (!mOutputted) {
+            if (mImageShow.hasModifications()) {
+                // Get the name of the album, to which the image will be saved
+                File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri());
+                int bucketId = GalleryUtils.getBucketId(saveDir.getPath());
+                String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null);
+                showSavingProgress(albumName);
+                mImageShow.saveImage(this, null);
+            } else {
+                done();
+            }
         }
     }
 
+    public boolean detectSpecialExitCases() {
+        return mCropExtras != null && (mCropExtras.getExtraOutput() != null
+                || mCropExtras.getSetAsWallpaper() || mCropExtras.getReturnData());
+    }
+
+    public void handleSpecialExitCases() {
+        if (mCropExtras != null) {
+            if (mCropExtras.getExtraOutput() != null) {
+                mSaveToExtraUri = true;
+                mOutputted = true;
+            }
+            if (mCropExtras.getSetAsWallpaper()) {
+                mSaveAsWallpaper = true;
+                mOutputted = true;
+            }
+            if (mCropExtras.getReturnData()) {
+                mReturnAsExtra = true;
+                mOutputted = true;
+            }
+            if (mOutputted) {
+                mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true);
+                showSavingProgress(null);
+                mImageShow.returnFilteredResult(this);
+            }
+        }
+    }
+
+    public void onFilteredResult(Bitmap filtered) {
+        Intent intent = new Intent();
+        intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds());
+        if (mSaveToExtraUri) {
+            mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(),
+                    mCropExtras.getOutputFormat(), this);
+        }
+        if (mSaveAsWallpaper) {
+            setWallpaperInBackground(filtered);
+        }
+        if (mReturnAsExtra) {
+            if (filtered != null) {
+                int bmapSize = filtered.getRowBytes() * filtered.getHeight();
+                /*
+                 * Max size of Binder transaction buffer is 1Mb, so constrain
+                 * Bitmap to be somewhat less than this, otherwise we get
+                 * TransactionTooLargeExceptions.
+                 */
+                if (bmapSize > MAX_BMAP_IN_INTENT) {
+                    Log.w(LOGTAG, "Bitmap too large to be returned via intent");
+                } else {
+                    intent.putExtra(CropExtras.KEY_DATA, filtered);
+                }
+            }
+        }
+        setResult(RESULT_OK, intent);
+        if (!mSaveToExtraUri) {
+            done();
+        }
+    }
+
+    void setWallpaperInBackground(final Bitmap bmap) {
+        Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+        BitmapTask.Callbacks<FilterShowActivity> cb = new BitmapTask.Callbacks<FilterShowActivity>() {
+            @Override
+            public void onComplete(Bitmap result) {}
+
+            @Override
+            public void onCancel() {}
+
+            @Override
+            public Bitmap onExecute(FilterShowActivity param) {
+                try {
+                    WallpaperManager.getInstance(param).setBitmap(bmap);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "fail to set wall paper", e);
+                }
+                return null;
+            }
+        };
+        (new BitmapTask<FilterShowActivity>(cb)).execute(this);
+    }
+
+    public void done() {
+        if (mOutputted) {
+            hideSavingProgress();
+        }
+        finish();
+    }
+
     static {
         System.loadLibrary("jni_filtershow_filters");
     }
diff --git a/src/com/android/gallery3d/filtershow/HistoryAdapter.java b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
index 4719992..79460be 100644
--- a/src/com/android/gallery3d/filtershow/HistoryAdapter.java
+++ b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
@@ -17,6 +17,9 @@
 package com.android.gallery3d.filtershow;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -42,6 +45,8 @@
     private MenuItem mRedoMenuItem = null;
     private MenuItem mResetMenuItem = null;
 
+    private Bitmap mOriginalBitmap = null;
+
     public HistoryAdapter(Context context, int resource, int textViewResourceId) {
         super(context, resource, textViewResourceId);
         FilterShowActivity activity = (FilterShowActivity) context;
@@ -82,13 +87,21 @@
 
     public void updateMenuItems() {
         if (mUndoMenuItem != null) {
-            mUndoMenuItem.setEnabled(canUndo());
+            setEnabled(mUndoMenuItem, canUndo());
         }
         if (mRedoMenuItem != null) {
-            mRedoMenuItem.setEnabled(canRedo());
+            setEnabled(mRedoMenuItem, canRedo());
         }
         if (mResetMenuItem != null) {
-            mResetMenuItem.setEnabled(canReset());
+            setEnabled(mResetMenuItem, canReset());
+        }
+    }
+
+    private void setEnabled(MenuItem item, boolean enabled) {
+        item.setEnabled(enabled);
+        Drawable drawable = item.getIcon();
+        if (drawable != null) {
+            drawable.setAlpha(enabled ? 255 : 80);
         }
     }
 
@@ -115,22 +128,13 @@
         return getItem(0);
     }
 
-    public void addHistoryItem(ImagePreset preset) {
-        if (canAddHistoryItem(preset)) {
-            insert(preset, 0);
-            updateMenuItems();
-        }
+    public ImagePreset getCurrent() {
+        return getItem(mCurrentPresetPosition);
     }
 
-    public boolean canAddHistoryItem(ImagePreset preset) {
-        if (getCount() > 0 && getLast().same(preset)) {
-            // we may still want to insert if the previous
-            // history element isn't the same
-            if (getLast().historyName().equalsIgnoreCase(preset.historyName())) {
-                return false;
-            }
-        }
-        return true;
+    public void addHistoryItem(ImagePreset preset) {
+        insert(preset, 0);
+        updateMenuItems();
     }
 
     @Override
@@ -147,9 +151,6 @@
             }
             mCurrentPresetPosition = position;
             this.notifyDataSetChanged();
-            if (!canAddHistoryItem(preset)) {
-                return;
-            }
         }
         super.insert(preset, position);
         mCurrentPresetPosition = position;
@@ -191,34 +192,28 @@
             if (itemView != null) {
                 itemView.setText(item.historyName());
             }
-            ImageView markView = (ImageView) view.findViewById(R.id.selectedMark);
-            if (position == mCurrentPresetPosition) {
-                markView.setVisibility(View.VISIBLE);
-            } else {
-                markView.setVisibility(View.INVISIBLE);
+            ImageView preview = (ImageView) view.findViewById(R.id.preview);
+            Bitmap bmp = item.getPreviewImage();
+            if (position == getCount()-1 && mOriginalBitmap != null) {
+                bmp = mOriginalBitmap;
             }
-            ImageView typeView = (ImageView) view.findViewById(R.id.typeMark);
-            // TODO: use type of last filter, not a string, to discriminate.
-            if (position == getCount() - 1) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_effects);
-            } else if (item.historyName().equalsIgnoreCase(mBorders)) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_border);
-            } else if (item.historyName().equalsIgnoreCase(mStraighten)) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_fix);
-            } else if (item.historyName().equalsIgnoreCase(mCrop)) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_fix);
-            } else if (item.historyName().equalsIgnoreCase(mRotate)) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_fix);
-            } else if (item.historyName().equalsIgnoreCase(mMirror)) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_fix);
-            } else if (item.isFx()) {
-                typeView.setImageResource(R.drawable.ic_photoeditor_effects);
+            if (bmp != null) {
+                preview.setImageBitmap(bmp);
             } else {
-                typeView.setImageResource(R.drawable.ic_photoeditor_color);
+                preview.setImageResource(android.R.color.transparent);
+            }
+            if (position == mCurrentPresetPosition) {
+                view.setBackgroundColor(Color.WHITE);
+            } else {
+                view.setBackgroundResource(R.color.background_main_toolbar);
             }
         }
 
         return view;
     }
 
+
+    public void setOriginalBitmap(Bitmap originalBitmap) {
+        mOriginalBitmap = originalBitmap;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java b/src/com/android/gallery3d/filtershow/ImageStateAdapter.java
deleted file mode 100644
index 44b94e4..0000000
--- a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.TextView;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-
-public class ImageStateAdapter extends ArrayAdapter<ImageFilter> {
-    private static final String LOGTAG = "ImageStateAdapter";
-
-    public ImageStateAdapter(Context context, int textViewResourceId) {
-        super(context, textViewResourceId);
-    }
-
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        View view = convertView;
-        if (view == null) {
-            LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
-                    Context.LAYOUT_INFLATER_SERVICE);
-            view = inflater.inflate(R.layout.filtershow_imagestate_row, null);
-        }
-        ImageFilter filter = getItem(position);
-        if (filter != null) {
-            TextView itemLabel = (TextView) view.findViewById(R.id.imagestate_label);
-            itemLabel.setText(filter.getName());
-            TextView itemParameter = (TextView) view.findViewById(R.id.imagestate_parameter);
-            itemParameter.setText("" + filter.getParameter());
-        }
-        return view;
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
deleted file mode 100644
index e92c549..0000000
--- a/src/com/android/gallery3d/filtershow/PanelController.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow;
-
-import android.content.Context;
-import android.text.Html;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewPropertyAnimator;
-import android.widget.PopupMenu;
-import android.widget.TextView;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterBwFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterContrast;
-import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
-import com.android.gallery3d.filtershow.filters.ImageFilterExposure;
-import com.android.gallery3d.filtershow.filters.ImageFilterHue;
-import com.android.gallery3d.filtershow.filters.ImageFilterRedEye;
-import com.android.gallery3d.filtershow.filters.ImageFilterSaturated;
-import com.android.gallery3d.filtershow.filters.ImageFilterShadows;
-import com.android.gallery3d.filtershow.filters.ImageFilterSharpen;
-import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
-import com.android.gallery3d.filtershow.filters.ImageFilterVibrance;
-import com.android.gallery3d.filtershow.filters.ImageFilterVignette;
-import com.android.gallery3d.filtershow.filters.ImageFilterWBalance;
-import com.android.gallery3d.filtershow.imageshow.ImageCrop;
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-import com.android.gallery3d.filtershow.ui.FramedTextButton;
-import com.android.gallery3d.filtershow.ui.ImageCurves;
-
-import java.util.HashMap;
-import java.util.Vector;
-
-public class PanelController implements OnClickListener {
-    private static int PANEL = 0;
-    private static int COMPONENT = 1;
-    private static int VERTICAL_MOVE = 0;
-    private static int HORIZONTAL_MOVE = 1;
-    private static final int ANIM_DURATION = 200;
-    private static final String LOGTAG = "PanelController";
-    private boolean mDisableFilterButtons = false;
-
-    class Panel {
-        private final View mView;
-        private final View mContainer;
-        private int mPosition = 0;
-        private final Vector<View> mSubviews = new Vector<View>();
-
-        public Panel(View view, View container, int position) {
-            mView = view;
-            mContainer = container;
-            mPosition = position;
-        }
-
-        public void addView(View view) {
-            mSubviews.add(view);
-        }
-
-        public int getPosition() {
-            return mPosition;
-        }
-
-        public ViewPropertyAnimator unselect(int newPos, int move) {
-            ViewPropertyAnimator anim = mContainer.animate();
-            mView.setSelected(false);
-            mContainer.setX(0);
-            mContainer.setY(0);
-            int delta = 0;
-            int w = mRowPanel.getWidth();
-            int h = mRowPanel.getHeight();
-            if (move == HORIZONTAL_MOVE) {
-                if (newPos > mPosition) {
-                    delta = -w;
-                } else {
-                    delta = w;
-                }
-                anim.x(delta);
-            } else if (move == VERTICAL_MOVE) {
-                anim.y(h);
-            }
-            anim.setDuration(ANIM_DURATION).withLayer().withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    mContainer.setVisibility(View.GONE);
-                }
-            });
-            return anim;
-        }
-
-        public ViewPropertyAnimator select(int oldPos, int move) {
-            mView.setSelected(true);
-            mContainer.setVisibility(View.VISIBLE);
-            mContainer.setX(0);
-            mContainer.setY(0);
-            ViewPropertyAnimator anim = mContainer.animate();
-            int w = mRowPanel.getWidth();
-            int h = mRowPanel.getHeight();
-            if (move == HORIZONTAL_MOVE) {
-                if (oldPos < mPosition) {
-                    mContainer.setX(w);
-                } else {
-                    mContainer.setX(-w);
-                }
-                anim.x(0);
-            } else if (move == VERTICAL_MOVE) {
-                mContainer.setY(h);
-                anim.y(0);
-            }
-            anim.setDuration(ANIM_DURATION).withLayer();
-            return anim;
-        }
-    }
-
-    class UtilityPanel {
-        private final Context mContext;
-        private final View mView;
-        private final TextView mTextView;
-        private boolean mSelected = false;
-        private String mEffectName = null;
-        private int mParameterValue = 0;
-        private boolean mShowParameterValue = false;
-        private View mAspectButton = null;
-        private View mCurvesButton = null;
-        boolean firstTimeCropDisplayed = true;
-
-        public UtilityPanel(Context context, View view, View textView,
-                View aspectButton, View curvesButton) {
-            mContext = context;
-            mView = view;
-            mTextView = (TextView) textView;
-            mAspectButton = aspectButton;
-            mCurvesButton = curvesButton;
-        }
-
-        public boolean selected() {
-            return mSelected;
-        }
-
-        public void setAspectButton(FramedTextButton button, int itemId) {
-            ImageCrop imageCrop = (ImageCrop) mCurrentImage;
-            switch (itemId) {
-                case R.id.crop_menu_1to1: {
-                    String t = mContext.getString(R.string.aspect1to1_effect);
-                    button.setText(t);
-                    imageCrop.apply(1, 1);
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_4to3: {
-                    String t = mContext.getString(R.string.aspect4to3_effect);
-                    button.setText(t);
-                    imageCrop.apply(4, 3);
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_3to4: {
-                    String t = mContext.getString(R.string.aspect3to4_effect);
-                    button.setText(t);
-                    imageCrop.apply(3, 4);
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_5to7: {
-                    String t = mContext.getString(R.string.aspect5to7_effect);
-                    button.setText(t);
-                    imageCrop.apply(5, 7);
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_7to5: {
-                    String t = mContext.getString(R.string.aspect7to5_effect);
-                    button.setText(t);
-                    imageCrop.apply(7, 5);
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_none: {
-                    String t = mContext.getString(R.string.aspectNone_effect);
-                    button.setText(t);
-                    imageCrop.applyClear();
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-                case R.id.crop_menu_original: {
-                    String t = mContext.getString(R.string.aspectOriginal_effect);
-                    button.setText(t);
-                    imageCrop.applyOriginal();
-                    imageCrop.setAspectString(t);
-                    break;
-                }
-            }
-            imageCrop.invalidate();
-        }
-
-        public void showAspectButtons() {
-            if (mAspectButton != null)
-                mAspectButton.setVisibility(View.VISIBLE);
-        }
-
-        public void hideAspectButtons() {
-            if (mAspectButton != null)
-                mAspectButton.setVisibility(View.GONE);
-        }
-
-        public void showCurvesButtons() {
-            if (mCurvesButton != null)
-                mCurvesButton.setVisibility(View.VISIBLE);
-        }
-
-        public void hideCurvesButtons() {
-            if (mCurvesButton != null)
-                mCurvesButton.setVisibility(View.GONE);
-        }
-
-        public void onNewValue(int value) {
-            mParameterValue = value;
-            updateText();
-        }
-
-        public void setEffectName(String effectName) {
-            mEffectName = effectName;
-            setShowParameter(true);
-        }
-
-        public void setShowParameter(boolean s) {
-            mShowParameterValue = s;
-            updateText();
-        }
-
-        public void updateText() {
-            String apply = mContext.getString(R.string.apply_effect);
-            if (mShowParameterValue) {
-                mTextView.setText(Html.fromHtml(apply + " " + mEffectName + " "
-                        + mParameterValue));
-            } else {
-                mTextView.setText(Html.fromHtml(apply + " " + mEffectName));
-            }
-        }
-
-        public ViewPropertyAnimator unselect() {
-            ViewPropertyAnimator anim = mView.animate();
-            mView.setX(0);
-            mView.setY(0);
-            int h = mRowPanel.getHeight();
-            anim.y(-h);
-            anim.setDuration(ANIM_DURATION).withLayer().withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    mView.setVisibility(View.GONE);
-                }
-            });
-            mSelected = false;
-            return anim;
-        }
-
-        public ViewPropertyAnimator select() {
-            mView.setVisibility(View.VISIBLE);
-            int h = mRowPanel.getHeight();
-            mView.setX(0);
-            mView.setY(-h);
-            updateText();
-            ViewPropertyAnimator anim = mView.animate();
-            anim.y(0);
-            anim.setDuration(ANIM_DURATION).withLayer();
-            mSelected = true;
-            return anim;
-        }
-    }
-
-    class ViewType {
-        private final int mType;
-        private final View mView;
-
-        public ViewType(View view, int type) {
-            mView = view;
-            mType = type;
-        }
-
-        public int type() {
-            return mType;
-        }
-    }
-
-    private final HashMap<View, Panel> mPanels = new HashMap<View, Panel>();
-    private final HashMap<View, ViewType> mViews = new HashMap<View, ViewType>();
-    private final Vector<View> mImageViews = new Vector<View>();
-    private View mCurrentPanel = null;
-    private View mRowPanel = null;
-    private UtilityPanel mUtilityPanel = null;
-    private ImageShow mMasterImage = null;
-    private ImageShow mCurrentImage = null;
-    private FilterShowActivity mActivity = null;
-
-    public void setActivity(FilterShowActivity activity) {
-        mActivity = activity;
-    }
-
-    public void addView(View view) {
-        view.setOnClickListener(this);
-        mViews.put(view, new ViewType(view, COMPONENT));
-    }
-
-    public void addPanel(View view, View container, int position) {
-        mPanels.put(view, new Panel(view, container, position));
-        view.setOnClickListener(this);
-        mViews.put(view, new ViewType(view, PANEL));
-    }
-
-    public void addComponent(View aPanel, View component) {
-        Panel panel = mPanels.get(aPanel);
-        if (panel == null) {
-            return;
-        }
-        panel.addView(component);
-        component.setOnClickListener(this);
-        mViews.put(component, new ViewType(component, COMPONENT));
-    }
-
-    public void addImageView(View view) {
-        mImageViews.add(view);
-        ImageShow imageShow = (ImageShow) view;
-        imageShow.setPanelController(this);
-    }
-
-    public void resetParameters() {
-        showPanel(mCurrentPanel);
-        if (mCurrentImage != null) {
-            mCurrentImage.resetParameter();
-            mCurrentImage.select();
-        }
-        if (mDisableFilterButtons) {
-            mActivity.enableFilterButtons();
-            mDisableFilterButtons = false;
-        }
-    }
-
-    public boolean onBackPressed() {
-        if (mUtilityPanel == null || !mUtilityPanel.selected()) {
-            return true;
-        }
-        HistoryAdapter adapter = mMasterImage.getHistory();
-        int position = adapter.undo();
-        mMasterImage.onItemClick(position);
-        showPanel(mCurrentPanel);
-        mCurrentImage.select();
-        if (mDisableFilterButtons) {
-            mActivity.enableFilterButtons();
-            mActivity.resetHistory();
-            mDisableFilterButtons = false;
-        }
-        return false;
-    }
-
-    public void onNewValue(int value) {
-        mUtilityPanel.onNewValue(value);
-    }
-
-    public void showParameter(boolean s) {
-        mUtilityPanel.setShowParameter(s);
-    }
-
-    public void setCurrentPanel(View panel) {
-        showPanel(panel);
-    }
-
-    public void setRowPanel(View rowPanel) {
-        mRowPanel = rowPanel;
-    }
-
-    public void setUtilityPanel(Context context, View utilityPanel, View textView,
-            View aspectButton, View curvesButton) {
-        mUtilityPanel = new UtilityPanel(context, utilityPanel, textView,
-                aspectButton, curvesButton);
-    }
-
-    public void setMasterImage(ImageShow imageShow) {
-        mMasterImage = imageShow;
-    }
-
-    @Override
-    public void onClick(View view) {
-        ViewType type = mViews.get(view);
-        if (type.type() == PANEL) {
-            showPanel(view);
-        } else if (type.type() == COMPONENT) {
-            showComponent(view);
-        }
-    }
-
-    public ImageShow showImageView(int id) {
-        ImageShow image = null;
-        for (View view : mImageViews) {
-            if (view.getId() == id) {
-                view.setVisibility(View.VISIBLE);
-                image = (ImageShow) view;
-            } else {
-                view.setVisibility(View.GONE);
-            }
-        }
-        return image;
-    }
-
-    public void showDefaultImageView() {
-        showImageView(R.id.imageShow).setShowControls(false);
-        mMasterImage.setCurrentFilter(null);
-    }
-
-    public void showPanel(View view) {
-        view.setVisibility(View.VISIBLE);
-        boolean removedUtilityPanel = false;
-        Panel current = mPanels.get(mCurrentPanel);
-        if (mUtilityPanel != null && mUtilityPanel.selected()) {
-            ViewPropertyAnimator anim1 = mUtilityPanel.unselect();
-            removedUtilityPanel = true;
-            anim1.start();
-            if (mCurrentPanel == view) {
-                ViewPropertyAnimator anim2 = current.select(-1, VERTICAL_MOVE);
-                anim2.start();
-                showDefaultImageView();
-            }
-        }
-
-        if (mCurrentPanel == view) {
-            return;
-        }
-
-        Panel panel = mPanels.get(view);
-        if (!removedUtilityPanel) {
-            int currentPos = -1;
-            if (current != null) {
-                currentPos = current.getPosition();
-            }
-            ViewPropertyAnimator anim1 = panel.select(currentPos, HORIZONTAL_MOVE);
-            anim1.start();
-            if (current != null) {
-                ViewPropertyAnimator anim2 = current.unselect(panel.getPosition(), HORIZONTAL_MOVE);
-                anim2.start();
-            }
-        } else {
-            ViewPropertyAnimator anim = panel.select(-1, VERTICAL_MOVE);
-            anim.start();
-        }
-        showDefaultImageView();
-        mCurrentPanel = view;
-    }
-
-    public ImagePreset getImagePreset() {
-        return mMasterImage.getImagePreset();
-    }
-
-    public ImageFilter setImagePreset(ImageFilter filter, String name) {
-        ImagePreset copy = new ImagePreset(getImagePreset());
-        copy.add(filter);
-        copy.setHistoryName(name);
-        copy.setIsFx(false);
-        mMasterImage.setImagePreset(copy);
-        return filter;
-    }
-
-    public void ensureFilter(String name) {
-        ImagePreset preset = getImagePreset();
-        ImageFilter filter = preset.getFilter(name);
-        if (filter != null) {
-            // If we already have a filter, we might still want
-            // to push it onto the history stack.
-            ImagePreset copy = new ImagePreset(getImagePreset());
-            copy.setHistoryName(name);
-            mMasterImage.setImagePreset(copy);
-            filter = copy.getFilter(name);
-        }
-
-        if (filter == null && name.equalsIgnoreCase(
-                mCurrentImage.getContext().getString(R.string.curvesRGB))) {
-            filter = setImagePreset(new ImageFilterCurves(), name);
-        }
-        if (filter == null && name.equalsIgnoreCase(
-                mCurrentImage.getContext().getString(R.string.tinyplanet))) {
-            filter = setImagePreset(new ImageFilterTinyPlanet(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vignette))) {
-            filter = setImagePreset(new ImageFilterVignette(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.sharpness))) {
-            filter = setImagePreset(new ImageFilterSharpen(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.contrast))) {
-            filter = setImagePreset(new ImageFilterContrast(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.saturation))) {
-            filter = setImagePreset(new ImageFilterSaturated(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.bwfilter))) {
-            filter = setImagePreset(new ImageFilterBwFilter(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.hue))) {
-            filter = setImagePreset(new ImageFilterHue(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.exposure))) {
-            filter = setImagePreset(new ImageFilterExposure(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vibrance))) {
-            filter = setImagePreset(new ImageFilterVibrance(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(
-                        R.string.shadow_recovery))) {
-            filter = setImagePreset(new ImageFilterShadows(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.redeye))) {
-            filter = setImagePreset(new ImageFilterRedEye(), name);
-        }
-        if (filter == null
-                && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.wbalance))) {
-            filter = setImagePreset(new ImageFilterWBalance(), name);
-        }
-        mMasterImage.setCurrentFilter(filter);
-    }
-
-    private void showCurvesPopupMenu(final ImageCurves curves, final FramedTextButton anchor) {
-        PopupMenu popupMenu = new PopupMenu(mCurrentImage.getContext(), anchor);
-        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
-        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
-            @Override
-            public boolean onMenuItemClick(MenuItem item) {
-                curves.setChannel(item.getItemId());
-                anchor.setTextFrom(item.getItemId());
-                return true;
-            }
-        });
-        popupMenu.show();
-    }
-
-    private void showCropPopupMenu(final FramedTextButton anchor) {
-        PopupMenu popupMenu = new PopupMenu(mCurrentImage.getContext(), anchor);
-        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_crop, popupMenu.getMenu());
-        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
-            @Override
-            public boolean onMenuItemClick(MenuItem item) {
-                mUtilityPanel.setAspectButton(anchor, item.getItemId());
-                return true;
-            }
-        });
-        popupMenu.show();
-    }
-
-    public void showComponent(View view) {
-        if (mUtilityPanel != null && !mUtilityPanel.selected()) {
-            Panel current = mPanels.get(mCurrentPanel);
-            ViewPropertyAnimator anim1 = current.unselect(-1, VERTICAL_MOVE);
-            anim1.start();
-            if (mUtilityPanel != null) {
-                ViewPropertyAnimator anim2 = mUtilityPanel.select();
-                anim2.start();
-            }
-        }
-
-        if (view.getId() == R.id.pickCurvesChannel) {
-            ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves);
-            showCurvesPopupMenu(curves, (FramedTextButton) view);
-            return;
-        }
-
-        if (view.getId() == R.id.aspect) {
-            showCropPopupMenu((FramedTextButton) view);
-            return;
-        }
-
-        if (mCurrentImage != null) {
-            mCurrentImage.unselect();
-        }
-        mUtilityPanel.hideAspectButtons();
-        mUtilityPanel.hideCurvesButtons();
-        switch (view.getId()) {
-            case R.id.tinyplanetButton: {
-                mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.tinyplanet);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                if (!mDisableFilterButtons) {
-                    mActivity.disableFilterButtons();
-                    mDisableFilterButtons = true;
-                }
-                break;
-            }
-            case R.id.straightenButton: {
-                mCurrentImage = showImageView(R.id.imageStraighten);
-                String ename = mCurrentImage.getContext().getString(R.string.straighten);
-                mUtilityPanel.setEffectName(ename);
-                break;
-            }
-            case R.id.cropButton: {
-                mCurrentImage = showImageView(R.id.imageCrop);
-                String ename = mCurrentImage.getContext().getString(R.string.crop);
-                mUtilityPanel.setEffectName(ename);
-                mUtilityPanel.setShowParameter(false);
-                if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed){
-                    ((ImageCrop) mCurrentImage).applyClear();
-                    mUtilityPanel.firstTimeCropDisplayed = false;
-                }
-                mUtilityPanel.showAspectButtons();
-                break;
-            }
-            case R.id.rotateButton: {
-                mCurrentImage = showImageView(R.id.imageRotate);
-                String ename = mCurrentImage.getContext().getString(R.string.rotate);
-                mUtilityPanel.setEffectName(ename);
-                break;
-            }
-            case R.id.flipButton: {
-                mCurrentImage = showImageView(R.id.imageFlip);
-                String ename = mCurrentImage.getContext().getString(R.string.mirror);
-                mUtilityPanel.setEffectName(ename);
-                mUtilityPanel.setShowParameter(false);
-                break;
-            }
-            case R.id.vignetteButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.vignette);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.curvesButtonRGB: {
-                ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves);
-                String ename = curves.getContext().getString(R.string.curvesRGB);
-                mUtilityPanel.setEffectName(ename);
-                mUtilityPanel.setShowParameter(false);
-                mUtilityPanel.showCurvesButtons();
-                mCurrentImage = curves;
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.sharpenButton: {
-                mCurrentImage = showImageView(R.id.imageZoom).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.sharpness);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.contrastButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.contrast);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.saturationButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.saturation);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.bwfilterButton: {
-            mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-            String ename = mCurrentImage.getContext().getString(R.string.bwfilter);
-            mUtilityPanel.setEffectName(ename);
-            ensureFilter(ename);
-            break;
-        }
-            case R.id.wbalanceButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(false);
-                String ename = mCurrentImage.getContext().getString(R.string.wbalance);
-                mUtilityPanel.setEffectName(ename);
-                mUtilityPanel.setShowParameter(false);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.hueButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.hue);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.exposureButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.exposure);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.vibranceButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.vibrance);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.shadowRecoveryButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.shadow_recovery);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.redEyeButton: {
-                mCurrentImage = showImageView(R.id.imageShow).setShowControls(true);
-                String ename = mCurrentImage.getContext().getString(R.string.redeye);
-                mUtilityPanel.setEffectName(ename);
-                ensureFilter(ename);
-                break;
-            }
-            case R.id.aspect: {
-                mUtilityPanel.showAspectButtons();
-                break;
-            }
-            case R.id.applyEffect: {
-                if (mMasterImage.getCurrentFilter() instanceof ImageFilterTinyPlanet) {
-                    mActivity.saveImage();
-                } else {
-                    if (mCurrentImage instanceof ImageCrop) {
-                        ((ImageCrop) mCurrentImage).saveAndSetPreset();
-                    }
-                    showPanel(mCurrentPanel);
-                }
-                break;
-            }
-        }
-        mCurrentImage.select();
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/BitmapCache.java b/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
deleted file mode 100644
index ba623c3..0000000
--- a/src/com/android/gallery3d/filtershow/cache/BitmapCache.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.cache;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
-import java.nio.ByteBuffer;
-
-public class BitmapCache {
-
-    private static final String LOGTAG = "BitmapCache";
-    static int mNbItems = 20;
-    private final Bitmap[] mBitmaps = new Bitmap[mNbItems];
-    private final Object[] mKeys = new Object[mNbItems];
-    private final long[] mIndices = new long[mNbItems];
-    private final boolean[] mBusyStatus = new boolean[mNbItems];
-
-    private Bitmap mOriginalBitmap = null;
-    private ByteBuffer mBuffer = null;
-    private final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
-    private long mIndex = 0;
-
-    public void setOriginalBitmap(Bitmap original) {
-        if (original == null) {
-            return;
-        }
-        mOriginalBitmap = original.copy(mConfig, true);
-        int size = 4 * original.getWidth() * original.getHeight();
-        mBuffer = ByteBuffer.allocate(size);
-        mOriginalBitmap.copyPixelsToBuffer(mBuffer);
-        mBuffer.rewind();
-        mOriginalBitmap.copyPixelsFromBuffer(mBuffer);
-        for (int i = 0; i < mNbItems; i++) {
-            mBitmaps[i] = mOriginalBitmap.copy(mConfig, true);
-        }
-    }
-
-    private int getOldestPosition() {
-        long minIndex = mIndices[0];
-        int current = 0;
-        for (int i = 1; i < mNbItems; i++) {
-            if (!mBusyStatus[i] && minIndex > mIndices[i]) {
-                minIndex = mIndices[i];
-                current = i;
-            }
-        }
-        return current;
-    }
-
-    public Bitmap put(ImagePreset preset) {
-        int pos = getOldestPosition();
-        return put(preset, pos);
-    }
-
-    public Bitmap put(ImagePreset preset, int pos) {
-        mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
-        Bitmap bitmap = mBitmaps[pos];
-        bitmap = preset.apply(bitmap);
-        mKeys[pos] = preset;
-        mIndices[pos] = mIndex++;
-        return bitmap;
-    }
-
-    public int reservePosition(ImagePreset preset) {
-        for (int i = 1; i < mNbItems; i++) {
-            if (mKeys[i] == preset && mBusyStatus[i]) {
-                return -1;
-            }
-        }
-        int pos = getOldestPosition();
-        mBusyStatus[pos] = true;
-        mKeys[pos] = preset;
-        return pos;
-    }
-
-    public void processPosition(int pos) {
-        ImagePreset preset = (ImagePreset) mKeys[pos];
-        mBitmaps[pos] = mOriginalBitmap.copy(mConfig, true);
-        Bitmap bitmap = mBitmaps[pos];
-        bitmap = preset.apply(bitmap);
-        mIndices[pos] = mIndex++;
-    }
-
-    public void unlockPosition(int pos) {
-        mBusyStatus[pos] = false;
-    }
-
-    public Bitmap get(ImagePreset preset) {
-        int foundPosition = -1;
-        int currentIndice = 0;
-        for (int i = 0; i < mNbItems; i++) {
-            if (mKeys[i] == preset && mBitmaps[i] != null) {
-                if (mIndices[i] > currentIndice) {
-                    foundPosition = i;
-                }
-            }
-        }
-        if (foundPosition != -1) {
-            mIndices[foundPosition] = mIndex++;
-            return mBitmaps[foundPosition];
-        }
-        return null;
-    }
-
-    public void reset(ImagePreset preset) {
-        for (int i = 0; i < mNbItems; i++) {
-            if (mKeys[i] == preset && !mBusyStatus[i]) {
-                mBitmaps[i] = null;
-            }
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/Cache.java b/src/com/android/gallery3d/filtershow/cache/Cache.java
deleted file mode 100644
index 7a3ce9d..0000000
--- a/src/com/android/gallery3d/filtershow/cache/Cache.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.cache;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
-public interface Cache {
-    public void setOriginalBitmap(Bitmap bitmap);
-
-    public void reset(ImagePreset preset);
-
-    public void prepare(ImagePreset preset);
-
-    public Bitmap get(ImagePreset preset);
-
-    public void addObserver(ImageShow observer);
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
new file mode 100644
index 0000000..8760c4a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
@@ -0,0 +1,461 @@
+/*
+ * 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.gallery3d.filtershow.cache;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.RenderScript;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.FilterEnvironment;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class CachingPipeline {
+    private static final String LOGTAG = "CachingPipeline";
+    private boolean DEBUG = false;
+
+    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
+
+    private static volatile RenderScript sRS = null;
+    private static volatile Resources sResources = null;
+
+    private FiltersManager mFiltersManager = null;
+    private volatile Bitmap mOriginalBitmap = null;
+    private volatile Bitmap mResizedOriginalBitmap = null;
+
+    private FilterEnvironment mEnvironment = new FilterEnvironment();
+
+    private volatile Allocation mOriginalAllocation = null;
+    private volatile Allocation mFiltersOnlyOriginalAllocation =  null;
+
+    protected volatile Allocation mInPixelsAllocation;
+    protected volatile Allocation mOutPixelsAllocation;
+    private volatile int mWidth = 0;
+    private volatile int mHeight = 0;
+
+    private volatile GeometryMetadata mPreviousGeometry = null;
+    private volatile float mPreviewScaleFactor = 1.0f;
+    private volatile float mHighResPreviewScaleFactor = 1.0f;
+    private volatile String mName = "";
+
+    private ImageFilterGeometry mGeometry = null;
+
+    public CachingPipeline(FiltersManager filtersManager, String name) {
+        mFiltersManager = filtersManager;
+        mName = name;
+    }
+
+    public static synchronized Resources getResources() {
+        return sResources;
+    }
+
+    public static synchronized void setResources(Resources resources) {
+        sResources = resources;
+    }
+
+    public static synchronized RenderScript getRenderScriptContext() {
+        return sRS;
+    }
+
+    public static synchronized void setRenderScriptContext(RenderScript RS) {
+        sRS = RS;
+    }
+
+    public static synchronized void createRenderscriptContext(Activity context) {
+        if (sRS != null) {
+            Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext");
+            destroyRenderScriptContext();
+        }
+        sRS = RenderScript.create(context);
+        sResources = context.getResources();
+    }
+
+    public static synchronized void destroyRenderScriptContext() {
+        if (sRS != null) {
+            sRS.destroy();
+        }
+        sRS = null;
+        sResources = null;
+    }
+
+    public void stop() {
+        mEnvironment.setStop(true);
+    }
+
+    public synchronized void reset() {
+        synchronized (CachingPipeline.class) {
+            if (getRenderScriptContext() == null) {
+                return;
+            }
+            mOriginalBitmap = null; // just a reference to the bitmap in ImageLoader
+            if (mResizedOriginalBitmap != null) {
+                mResizedOriginalBitmap.recycle();
+                mResizedOriginalBitmap = null;
+            }
+            if (mOriginalAllocation != null) {
+                mOriginalAllocation.destroy();
+                mOriginalAllocation = null;
+            }
+            if (mFiltersOnlyOriginalAllocation != null) {
+                mFiltersOnlyOriginalAllocation.destroy();
+                mFiltersOnlyOriginalAllocation = null;
+            }
+            mPreviousGeometry = null;
+            mPreviewScaleFactor = 1.0f;
+            mHighResPreviewScaleFactor = 1.0f;
+
+            destroyPixelAllocations();
+        }
+    }
+
+    private synchronized void destroyPixelAllocations() {
+        if (DEBUG) {
+            Log.v(LOGTAG, "destroyPixelAllocations in " + getName());
+        }
+        if (mInPixelsAllocation != null) {
+            mInPixelsAllocation.destroy();
+            mInPixelsAllocation = null;
+        }
+        if (mOutPixelsAllocation != null) {
+            mOutPixelsAllocation.destroy();
+            mOutPixelsAllocation = null;
+        }
+        mWidth = 0;
+        mHeight = 0;
+    }
+
+    private String getType(RenderingRequest request) {
+        if (request.getType() == RenderingRequest.ICON_RENDERING) {
+            return "ICON_RENDERING";
+        }
+        if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+            return "FILTERS_RENDERING";
+        }
+        if (request.getType() == RenderingRequest.FULL_RENDERING) {
+            return "FULL_RENDERING";
+        }
+        if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+            return "GEOMETRY_RENDERING";
+        }
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            return "PARTIAL_RENDERING";
+        }
+        if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+            return "HIGHRES_RENDERING";
+        }
+        return "UNKNOWN TYPE!";
+    }
+
+    private void setupEnvironment(ImagePreset preset, boolean highResPreview) {
+        mEnvironment.setCachingPipeline(this);
+        mEnvironment.setFiltersManager(mFiltersManager);
+        if (highResPreview) {
+            mEnvironment.setScaleFactor(mHighResPreviewScaleFactor);
+        } else {
+            mEnvironment.setScaleFactor(mPreviewScaleFactor);
+        }
+        mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
+        mEnvironment.setImagePreset(preset);
+        mEnvironment.setStop(false);
+    }
+
+    public void setOriginal(Bitmap bitmap) {
+        mOriginalBitmap = bitmap;
+        Log.v(LOGTAG,"setOriginal, size " + bitmap.getWidth() + " x " + bitmap.getHeight());
+        ImagePreset preset = MasterImage.getImage().getPreset();
+        setupEnvironment(preset, false);
+        updateOriginalAllocation(preset);
+    }
+
+    private synchronized boolean updateOriginalAllocation(ImagePreset preset) {
+        Bitmap originalBitmap = mOriginalBitmap;
+
+        if (originalBitmap == null) {
+            return false;
+        }
+
+        GeometryMetadata geometry = preset.getGeometry();
+        if (mPreviousGeometry != null && geometry.equals(mPreviousGeometry)) {
+            return false;
+        }
+
+        if (DEBUG) {
+            Log.v(LOGTAG, "geometry has changed");
+        }
+
+        RenderScript RS = getRenderScriptContext();
+
+        Allocation filtersOnlyOriginalAllocation = mFiltersOnlyOriginalAllocation;
+        mFiltersOnlyOriginalAllocation = Allocation.createFromBitmap(RS, originalBitmap,
+                Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+        if (filtersOnlyOriginalAllocation != null) {
+            filtersOnlyOriginalAllocation.destroy();
+        }
+
+        Allocation originalAllocation = mOriginalAllocation;
+        mResizedOriginalBitmap = preset.applyGeometry(originalBitmap, mEnvironment);
+        mOriginalAllocation = Allocation.createFromBitmap(RS, mResizedOriginalBitmap,
+                Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+        if (originalAllocation != null) {
+            originalAllocation.destroy();
+        }
+
+        mPreviousGeometry = new GeometryMetadata(geometry);
+        return true;
+    }
+
+    public synchronized void render(RenderingRequest request) {
+        synchronized (CachingPipeline.class) {
+            if (getRenderScriptContext() == null) {
+                return;
+            }
+            if (((request.getType() != RenderingRequest.PARTIAL_RENDERING
+                    && request.getType() != RenderingRequest.HIGHRES_RENDERING)
+                    && request.getBitmap() == null)
+                    || request.getImagePreset() == null) {
+                return;
+            }
+
+            if (DEBUG) {
+                Log.v(LOGTAG, "render image of type " + getType(request));
+            }
+
+            Bitmap bitmap = request.getBitmap();
+            ImagePreset preset = request.getImagePreset();
+            setupEnvironment(preset,
+                    request.getType() != RenderingRequest.HIGHRES_RENDERING);
+            mFiltersManager.freeFilterResources(preset);
+
+            if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+                ImageLoader loader = MasterImage.getImage().getImageLoader();
+                if (loader == null) {
+                    Log.w(LOGTAG, "loader not yet setup, cannot handle: " + getType(request));
+                    return;
+                }
+                bitmap = loader.getScaleOneImageForPreset(request.getBounds(),
+                        request.getDestination());
+                if (bitmap == null) {
+                    Log.w(LOGTAG, "could not get bitmap for: " + getType(request));
+                    return;
+                }
+            }
+
+            if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+                ImageLoader loader = MasterImage.getImage().getImageLoader();
+                bitmap = loader.getOriginalBitmapHighres();
+                bitmap = preset.applyGeometry(bitmap, mEnvironment);
+            }
+
+            if (request.getType() == RenderingRequest.FULL_RENDERING
+                    || request.getType() == RenderingRequest.GEOMETRY_RENDERING
+                    || request.getType() == RenderingRequest.FILTERS_RENDERING) {
+                updateOriginalAllocation(preset);
+            }
+
+            if (DEBUG) {
+                Log.v(LOGTAG, "after update, req bitmap (" + bitmap.getWidth() + "x" + bitmap.getHeight()
+                        + " ? resizeOriginal (" + mResizedOriginalBitmap.getWidth() + "x"
+                        + mResizedOriginalBitmap.getHeight());
+            }
+
+            if (request.getType() == RenderingRequest.FULL_RENDERING
+                    || request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+                mOriginalAllocation.copyTo(bitmap);
+            } else if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+                mFiltersOnlyOriginalAllocation.copyTo(bitmap);
+            }
+
+            if (request.getType() == RenderingRequest.FULL_RENDERING
+                    || request.getType() == RenderingRequest.FILTERS_RENDERING
+                    || request.getType() == RenderingRequest.ICON_RENDERING
+                    || request.getType() == RenderingRequest.PARTIAL_RENDERING
+                    || request.getType() == RenderingRequest.HIGHRES_RENDERING
+                    || request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
+
+                if (request.getType() == RenderingRequest.ICON_RENDERING) {
+                    mEnvironment.setQuality(ImagePreset.QUALITY_ICON);
+                } else  if (request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
+                    mEnvironment.setQuality(ImagePreset.STYLE_ICON);
+                } else {
+                    mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
+                }
+
+                Bitmap bmp = preset.apply(bitmap, mEnvironment);
+                if (!mEnvironment.needsStop()) {
+                    request.setBitmap(bmp);
+                }
+                mFiltersManager.freeFilterResources(preset);
+            }
+        }
+    }
+
+    public synchronized void renderImage(ImagePreset preset, Allocation in, Allocation out) {
+        synchronized (CachingPipeline.class) {
+            if (getRenderScriptContext() == null) {
+                return;
+            }
+            setupEnvironment(preset, false);
+            mFiltersManager.freeFilterResources(preset);
+            preset.applyFilters(-1, -1, in, out, mEnvironment);
+            // TODO: we should render the border onto a different bitmap instead
+            preset.applyBorder(in, out, mEnvironment);
+        }
+    }
+
+    public synchronized Bitmap renderFinalImage(Bitmap bitmap, ImagePreset preset) {
+        synchronized (CachingPipeline.class) {
+            if (getRenderScriptContext() == null) {
+                return bitmap;
+            }
+            setupEnvironment(preset, false);
+            mEnvironment.setQuality(ImagePreset.QUALITY_FINAL);
+            mEnvironment.setScaleFactor(1.0f);
+            mFiltersManager.freeFilterResources(preset);
+            bitmap = preset.applyGeometry(bitmap, mEnvironment);
+            bitmap = preset.apply(bitmap, mEnvironment);
+            return bitmap;
+        }
+    }
+
+    public Bitmap renderGeometryIcon(Bitmap bitmap, ImagePreset preset) {
+        // Called by RenderRequest on the main thread
+        // TODO: change this -- we should reuse a pool of bitmaps instead...
+        if (mGeometry == null) {
+            mGeometry = new ImageFilterGeometry();
+        }
+        mGeometry.useRepresentation(preset.getGeometry());
+        return mGeometry.apply(bitmap, mPreviewScaleFactor,
+                ImagePreset.QUALITY_PREVIEW);
+    }
+
+    public synchronized void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) {
+        synchronized (CachingPipeline.class) {
+            if (getRenderScriptContext() == null) {
+                return;
+            }
+            if (DEBUG) {
+                Log.v(LOGTAG, "compute preset " + preset);
+                preset.showFilters();
+            }
+
+            String thread = Thread.currentThread().getName();
+            long time = System.currentTimeMillis();
+            setupEnvironment(preset, false);
+            mFiltersManager.freeFilterResources(preset);
+
+            Bitmap resizedOriginalBitmap = mResizedOriginalBitmap;
+            if (updateOriginalAllocation(preset)) {
+                resizedOriginalBitmap = mResizedOriginalBitmap;
+                mEnvironment.cache(buffer.getProducer());
+                buffer.updateProducerBitmap(resizedOriginalBitmap);
+            }
+            Bitmap bitmap = buffer.getProducer();
+            long time2 = System.currentTimeMillis();
+
+            if (bitmap == null || (bitmap.getWidth() != resizedOriginalBitmap.getWidth())
+                    || (bitmap.getHeight() != resizedOriginalBitmap.getHeight())) {
+                mEnvironment.cache(buffer.getProducer());
+                buffer.updateProducerBitmap(resizedOriginalBitmap);
+                bitmap = buffer.getProducer();
+            }
+            mOriginalAllocation.copyTo(bitmap);
+
+            Bitmap tmpbitmap = preset.apply(bitmap, mEnvironment);
+            if (tmpbitmap != bitmap) {
+                mEnvironment.cache(buffer.getProducer());
+                buffer.setProducer(tmpbitmap);
+            }
+
+            mFiltersManager.freeFilterResources(preset);
+
+            time = System.currentTimeMillis() - time;
+            time2 = System.currentTimeMillis() - time2;
+            if (DEBUG) {
+                Log.v(LOGTAG, "Applying type " + type + " filters to bitmap "
+                        + bitmap + " (" + bitmap.getWidth() + " x " + bitmap.getHeight()
+                        + ") took " + time + " ms, " + time2 + " ms for the filter, on thread " + thread);
+            }
+        }
+    }
+
+    public boolean needsRepaint() {
+        TripleBufferBitmap buffer = MasterImage.getImage().getDoubleBuffer();
+        return buffer.checkRepaintNeeded();
+    }
+
+    public void setPreviewScaleFactor(float previewScaleFactor) {
+        mPreviewScaleFactor = previewScaleFactor;
+    }
+
+    public void setHighResPreviewScaleFactor(float highResPreviewScaleFactor) {
+        mHighResPreviewScaleFactor = highResPreviewScaleFactor;
+    }
+
+    public synchronized boolean isInitialized() {
+        return getRenderScriptContext() != null && mOriginalBitmap != null;
+    }
+
+    public boolean prepareRenderscriptAllocations(Bitmap bitmap) {
+        RenderScript RS = getRenderScriptContext();
+        boolean needsUpdate = false;
+        if (mOutPixelsAllocation == null || mInPixelsAllocation == null ||
+                bitmap.getWidth() != mWidth || bitmap.getHeight() != mHeight) {
+            destroyPixelAllocations();
+            Bitmap bitmapBuffer = bitmap;
+            if (bitmap.getConfig() == null || bitmap.getConfig() != BITMAP_CONFIG) {
+                bitmapBuffer = bitmap.copy(BITMAP_CONFIG, true);
+            }
+            mOutPixelsAllocation = Allocation.createFromBitmap(RS, bitmapBuffer,
+                    Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+            mInPixelsAllocation = Allocation.createTyped(RS,
+                    mOutPixelsAllocation.getType());
+            needsUpdate = true;
+        }
+        if (RS != null) {
+            mInPixelsAllocation.copyFrom(bitmap);
+        }
+        if (bitmap.getWidth() != mWidth
+                || bitmap.getHeight() != mHeight) {
+            mWidth = bitmap.getWidth();
+            mHeight = bitmap.getHeight();
+            needsUpdate = true;
+        }
+        if (DEBUG) {
+            Log.v(LOGTAG, "prepareRenderscriptAllocations: " + needsUpdate + " in " + getName());
+        }
+        return needsUpdate;
+    }
+
+    public synchronized Allocation getInPixelsAllocation() {
+        return mInPixelsAllocation;
+    }
+
+    public synchronized Allocation getOutPixelsAllocation() {
+        return mOutPixelsAllocation;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
deleted file mode 100644
index 408ba59..0000000
--- a/src/com/android/gallery3d/filtershow/cache/DelayedPresetCache.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.cache;
-
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.os.Process;
-
-public class DelayedPresetCache extends DirectPresetCache implements Callback {
-    private HandlerThread mHandlerThread = null;
-
-    private final static int NEW_PRESET = 0;
-    private final static int COMPUTE_PRESET = 1;
-
-    private Handler mProcessingHandler = null;
-    private final Handler mUIHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case NEW_PRESET: {
-                    CachedPreset cache = (CachedPreset) msg.obj;
-                    didCompute(cache);
-                    break;
-                }
-            }
-        }
-    };
-
-    @Override
-    public boolean handleMessage(Message msg) {
-        switch (msg.what) {
-            case COMPUTE_PRESET: {
-                CachedPreset cache = (CachedPreset) msg.obj;
-                compute(cache);
-                Message uimsg = mUIHandler.obtainMessage(NEW_PRESET, cache);
-                mUIHandler.sendMessage(uimsg);
-                break;
-            }
-        }
-        return false;
-    }
-
-    public DelayedPresetCache(ImageLoader loader, int size) {
-        super(loader, size);
-        mHandlerThread = new HandlerThread("ImageProcessing", Process.THREAD_PRIORITY_BACKGROUND);
-        mHandlerThread.start();
-        mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
-    }
-
-    @Override
-    protected void willCompute(CachedPreset cache) {
-        if (cache == null) {
-            return;
-        }
-        cache.setBusy(true);
-        Message msg = mProcessingHandler.obtainMessage(COMPUTE_PRESET, cache);
-        mProcessingHandler.sendMessage(msg);
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java b/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
deleted file mode 100644
index adbb5da..0000000
--- a/src/com/android/gallery3d/filtershow/cache/DirectPresetCache.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.cache;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
-import java.util.Vector;
-
-public class DirectPresetCache implements Cache {
-
-    private static final String LOGTAG = "DirectPresetCache";
-    private Bitmap mOriginalBitmap = null;
-    private final Vector<ImageShow> mObservers = new Vector<ImageShow>();
-    private final Vector<CachedPreset> mCache = new Vector<CachedPreset>();
-    private int mCacheSize = 1;
-    private final Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
-    private long mGlobalAge = 0;
-    private ImageLoader mLoader = null;
-
-    protected class CachedPreset {
-        private Bitmap mBitmap = null;
-        private ImagePreset mPreset = null;
-        private long mAge = 0;
-        private boolean mBusy = false;
-
-        public void setBusy(boolean value) {
-            mBusy = value;
-        }
-
-        public boolean busy() {
-            return mBusy;
-        }
-    }
-
-    public DirectPresetCache(ImageLoader loader, int size) {
-        mLoader = loader;
-        mCacheSize = size;
-    }
-
-    @Override
-    public void setOriginalBitmap(Bitmap bitmap) {
-        mOriginalBitmap = bitmap;
-        notifyObservers();
-    }
-
-    public void notifyObservers() {
-        mLoader.getActivity().runOnUiThread(mNotifyObserversRunnable);
-    }
-
-    private final Runnable mNotifyObserversRunnable = new Runnable() {
-        @Override
-        public void run() {
-            for (int i = 0; i < mObservers.size(); i++) {
-                ImageShow imageShow = mObservers.elementAt(i);
-                imageShow.invalidate();
-                imageShow.updateImage();
-            }
-        }
-    };
-
-    @Override
-    public void addObserver(ImageShow observer) {
-        if (!mObservers.contains(observer)) {
-            mObservers.add(observer);
-        }
-    }
-
-    private CachedPreset getCachedPreset(ImagePreset preset) {
-        for (int i = 0; i < mCache.size(); i++) {
-            CachedPreset cache = mCache.elementAt(i);
-            if (cache.mPreset == preset) {
-                return cache;
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Bitmap get(ImagePreset preset) {
-        // Log.v(LOGTAG, "get preset " + preset.name() + " : " + preset);
-        CachedPreset cache = getCachedPreset(preset);
-        if (cache != null && !cache.mBusy) {
-            return cache.mBitmap;
-        }
-        // Log.v(LOGTAG, "didn't find preset " + preset.name() + " : " + preset
-        // + " we have " + mCache.size() + " elts / " + mCacheSize);
-        return null;
-    }
-
-    @Override
-    public void reset(ImagePreset preset) {
-        CachedPreset cache = getCachedPreset(preset);
-        if (cache != null && !cache.mBusy) {
-            cache.mBitmap = null;
-            willCompute(cache);
-        }
-    }
-
-    private CachedPreset getOldestCachedPreset() {
-        CachedPreset found = null;
-        for (int i = 0; i < mCache.size(); i++) {
-            CachedPreset cache = mCache.elementAt(i);
-            if (cache.mBusy) {
-                continue;
-            }
-            if (found == null) {
-                found = cache;
-            } else {
-                if (found.mAge > cache.mAge) {
-                    found = cache;
-                }
-            }
-        }
-        return found;
-    }
-
-    protected void willCompute(CachedPreset cache) {
-        if (cache == null) {
-            return;
-        }
-        cache.mBusy = true;
-        compute(cache);
-        didCompute(cache);
-    }
-
-    protected void didCompute(CachedPreset cache) {
-        cache.mBusy = false;
-        notifyObservers();
-    }
-
-    protected void compute(CachedPreset cache) {
-        cache.mBitmap = null;
-        cache.mBitmap = mOriginalBitmap.copy(mBitmapConfig, true);
-        float scaleFactor = (float) cache.mBitmap.getWidth() / (float) mLoader.getOriginalBounds().width();
-        if (scaleFactor < 1.0f) {
-            cache.mPreset.setIsHighQuality(false);
-        }
-        cache.mPreset.setScaleFactor(scaleFactor);
-        cache.mBitmap = cache.mPreset.apply(cache.mBitmap);
-        cache.mAge = mGlobalAge++;
-    }
-
-    @Override
-    public void prepare(ImagePreset preset) {
-        // Log.v(LOGTAG, "prepare preset " + preset.name() + " : " + preset);
-        CachedPreset cache = getCachedPreset(preset);
-        if (cache == null || (cache.mBitmap == null && !cache.mBusy)) {
-            if (cache == null) {
-                if (mCache.size() < mCacheSize) {
-                    cache = new CachedPreset();
-                    mCache.add(cache);
-                } else {
-                    cache = getOldestCachedPreset();
-                }
-            }
-            if (cache != null) {
-                cache.mPreset = preset;
-                willCompute(cache);
-            }
-        }
-
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
new file mode 100644
index 0000000..a0b2897
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java
@@ -0,0 +1,235 @@
+/*
+ * 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.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+import android.os.*;
+import android.os.Process;
+import android.support.v8.renderscript.*;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilterRS;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class FilteringPipeline implements Handler.Callback {
+
+    private static volatile FilteringPipeline sPipeline = null;
+    private static final String LOGTAG = "FilteringPipeline";
+    private boolean DEBUG = false;
+
+    private static long HIRES_DELAY = 300; // in ms
+
+    private volatile boolean mPipelineIsOn = false;
+
+    private CachingPipeline mAccessoryPipeline = null;
+    private CachingPipeline mPreviewPipeline = null;
+    private CachingPipeline mHighresPreviewPipeline = null;
+
+    private HandlerThread mHandlerThread = null;
+    private final static int NEW_PRESET = 0;
+    private final static int NEW_RENDERING_REQUEST = 1;
+    private final static int COMPUTE_PRESET = 2;
+    private final static int COMPUTE_RENDERING_REQUEST = 3;
+    private final static int COMPUTE_PARTIAL_RENDERING_REQUEST = 4;
+    private final static int COMPUTE_HIGHRES_RENDERING_REQUEST = 5;
+
+    private volatile boolean mHasUnhandledPreviewRequest = false;
+
+    private String getType(int value) {
+        if (value == COMPUTE_RENDERING_REQUEST) {
+            return "COMPUTE_RENDERING_REQUEST";
+        }
+        if (value == COMPUTE_PARTIAL_RENDERING_REQUEST) {
+            return "COMPUTE_PARTIAL_RENDERING_REQUEST";
+        }
+        if (value == COMPUTE_HIGHRES_RENDERING_REQUEST) {
+            return "COMPUTE_HIGHRES_RENDERING_REQUEST";
+        }
+        return "UNKNOWN TYPE";
+    }
+
+    private Handler mProcessingHandler = null;
+    private final Handler mUIHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case NEW_PRESET: {
+                    TripleBufferBitmap buffer = MasterImage.getImage().getDoubleBuffer();
+                    buffer.swapConsumer();
+                    MasterImage.getImage().notifyObservers();
+                    if (mHasUnhandledPreviewRequest) {
+                        updatePreviewBuffer();
+                    }
+                    break;
+                }
+                case NEW_RENDERING_REQUEST: {
+                    RenderingRequest request = (RenderingRequest) msg.obj;
+                    request.markAvailable();
+                    break;
+                }
+            }
+        }
+    };
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        if (!mPipelineIsOn) {
+            return false;
+        }
+        switch (msg.what) {
+            case COMPUTE_PRESET: {
+                ImagePreset preset = (ImagePreset) msg.obj;
+                TripleBufferBitmap buffer = MasterImage.getImage().getDoubleBuffer();
+                mPreviewPipeline.compute(buffer, preset, COMPUTE_PRESET);
+                buffer.swapProducer();
+                Message uimsg = mUIHandler.obtainMessage(NEW_PRESET);
+                mUIHandler.sendMessage(uimsg);
+                break;
+            }
+            case COMPUTE_RENDERING_REQUEST:
+            case COMPUTE_PARTIAL_RENDERING_REQUEST:
+            case COMPUTE_HIGHRES_RENDERING_REQUEST: {
+
+                if (DEBUG) {
+                    Log.v(LOGTAG, "Compute Request: " + getType(msg.what));
+                }
+
+                RenderingRequest request = (RenderingRequest) msg.obj;
+                if (msg.what == COMPUTE_HIGHRES_RENDERING_REQUEST) {
+                    mHighresPreviewPipeline.render(request);
+                } else {
+                    mAccessoryPipeline.render(request);
+                }
+                if (request.getBitmap() != null) {
+                    Message uimsg = mUIHandler.obtainMessage(NEW_RENDERING_REQUEST);
+                    uimsg.obj = request;
+                    mUIHandler.sendMessage(uimsg);
+                }
+                break;
+            }
+        }
+        return false;
+    }
+
+    private FilteringPipeline() {
+        mHandlerThread = new HandlerThread("FilteringPipeline",
+                Process.THREAD_PRIORITY_FOREGROUND);
+        mHandlerThread.start();
+        mProcessingHandler = new Handler(mHandlerThread.getLooper(), this);
+        mAccessoryPipeline = new CachingPipeline(
+                FiltersManager.getManager(), "Accessory");
+        mPreviewPipeline = new CachingPipeline(
+                FiltersManager.getPreviewManager(), "Preview");
+        mHighresPreviewPipeline = new CachingPipeline(
+                FiltersManager.getHighresManager(), "Highres");
+    }
+
+    public synchronized static FilteringPipeline getPipeline() {
+        if (sPipeline == null) {
+            sPipeline = new FilteringPipeline();
+        }
+        return sPipeline;
+    }
+
+    public void setOriginal(Bitmap bitmap) {
+        if (mPipelineIsOn) {
+            Log.e(LOGTAG, "setOriginal called after pipeline initialization!");
+            return;
+        }
+        mAccessoryPipeline.setOriginal(bitmap);
+        mPreviewPipeline.setOriginal(bitmap);
+        mHighresPreviewPipeline.setOriginal(bitmap);
+    }
+
+    public void postRenderingRequest(RenderingRequest request) {
+        if (!mPipelineIsOn) {
+            return;
+        }
+        int type = COMPUTE_RENDERING_REQUEST;
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING) {
+            type = COMPUTE_PARTIAL_RENDERING_REQUEST;
+        }
+        if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+            type = COMPUTE_HIGHRES_RENDERING_REQUEST;
+        }
+        Message msg = mProcessingHandler.obtainMessage(type);
+        msg.obj = request;
+        if (type == COMPUTE_PARTIAL_RENDERING_REQUEST
+                || type == COMPUTE_HIGHRES_RENDERING_REQUEST) {
+            if (mProcessingHandler.hasMessages(msg.what)) {
+                mProcessingHandler.removeMessages(msg.what);
+            }
+            mProcessingHandler.sendMessageDelayed(msg, HIRES_DELAY);
+        } else {
+            mProcessingHandler.sendMessage(msg);
+        }
+    }
+
+    public void updatePreviewBuffer() {
+        if (!mPipelineIsOn) {
+            return;
+        }
+        mHasUnhandledPreviewRequest = true;
+        mHighresPreviewPipeline.stop();
+        if (mProcessingHandler.hasMessages(COMPUTE_PRESET)) {
+            return;
+        }
+        if (!mPreviewPipeline.needsRepaint()) {
+            return;
+        }
+        if (MasterImage.getImage().getPreset() == null) {
+            return;
+        }
+        Message msg = mProcessingHandler.obtainMessage(COMPUTE_PRESET);
+        msg.obj = MasterImage.getImage().getPreset();
+        mHasUnhandledPreviewRequest = false;
+        mProcessingHandler.sendMessageAtFrontOfQueue(msg);
+    }
+
+    public void setPreviewScaleFactor(float previewScaleFactor) {
+        mAccessoryPipeline.setPreviewScaleFactor(previewScaleFactor);
+        mPreviewPipeline.setPreviewScaleFactor(previewScaleFactor);
+        mHighresPreviewPipeline.setPreviewScaleFactor(previewScaleFactor);
+    }
+
+    public void setHighResPreviewScaleFactor(float highResPreviewScaleFactor) {
+        mAccessoryPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
+        mPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
+        mHighresPreviewPipeline.setHighResPreviewScaleFactor(highResPreviewScaleFactor);
+    }
+
+    public static synchronized void reset() {
+        sPipeline.mAccessoryPipeline.reset();
+        sPipeline.mPreviewPipeline.reset();
+        sPipeline.mHighresPreviewPipeline.reset();
+        sPipeline.mHandlerThread.quit();
+        sPipeline = null;
+    }
+
+    public void turnOnPipeline(boolean t) {
+        mPipelineIsOn = t;
+        if (mPipelineIsOn) {
+            assert(mPreviewPipeline.isInitialized());
+            assert(mAccessoryPipeline.isInitialized());
+            assert(mHighresPreviewPipeline.isInitialized());
+            updatePreviewBuffer();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
index afef58a..b4e98e1 100644
--- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
+++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java
@@ -22,46 +22,55 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.media.ExifInterface;
+import android.graphics.Bitmap.CompressFormat;
 import android.net.Uri;
 import android.provider.MediaStore;
 import android.util.Log;
 
 import com.adobe.xmp.XMPException;
 import com.adobe.xmp.XMPMeta;
-
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.ExifInterface;
 import com.android.gallery3d.filtershow.FilterShowActivity;
 import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.imageshow.ImageCrop;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.tools.BitmapTask;
 import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+import com.android.gallery3d.util.InterruptableOutputStream;
 import com.android.gallery3d.util.XmpUtilHelper;
 
+import java.io.ByteArrayInputStream;
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Vector;
 import java.util.concurrent.locks.ReentrantLock;
 
+
+// TODO: this class has waaaay to much bitmap copying.  Cleanup.
 public class ImageLoader {
 
     private static final String LOGTAG = "ImageLoader";
     private final Vector<ImageShow> mListeners = new Vector<ImageShow>();
     private Bitmap mOriginalBitmapSmall = null;
     private Bitmap mOriginalBitmapLarge = null;
+    private Bitmap mOriginalBitmapHighres = null;
     private Bitmap mBackgroundBitmap = null;
 
-    private Cache mCache = null;
-    private Cache mHiresCache = null;
     private final ZoomCache mZoomCache = new ZoomCache();
 
     private int mOrientation = 0;
@@ -69,28 +78,34 @@
 
     private FilterShowActivity mActivity = null;
 
-    public static final int ORI_NORMAL     = ExifInterface.ORIENTATION_NORMAL;
-    public static final int ORI_ROTATE_90  = ExifInterface.ORIENTATION_ROTATE_90;
-    public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180;
-    public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270;
-    public static final int ORI_FLIP_HOR   = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
-    public static final int ORI_FLIP_VERT  = ExifInterface.ORIENTATION_FLIP_VERTICAL;
-    public static final int ORI_TRANSPOSE  = ExifInterface.ORIENTATION_TRANSPOSE;
-    public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
+    public static final String JPEG_MIME_TYPE = "image/jpeg";
 
+    public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
+    public static final int DEFAULT_COMPRESS_QUALITY = 95;
+
+    public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
+    public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
+    public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
+    public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
+    public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
+    public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
+    public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
+    public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
+
+    private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
     private Context mContext = null;
     private Uri mUri = null;
 
     private Rect mOriginalBounds = null;
     private static int mZoomOrientation = ORI_NORMAL;
 
+    static final int MAX_BITMAP_DIM = 900;
+
     private ReentrantLock mLoadingLock = new ReentrantLock();
 
     public ImageLoader(FilterShowActivity activity, Context context) {
         mActivity = activity;
         mContext = context;
-        mCache = new DelayedPresetCache(this, 30);
-        mHiresCache = new DelayedPresetCache(this, 3);
     }
 
     public static int getZoomOrientation() {
@@ -101,18 +116,31 @@
         return mActivity;
     }
 
-    public void loadBitmap(Uri uri,int size) {
+    public boolean loadBitmap(Uri uri, int size) {
         mLoadingLock.lock();
         mUri = uri;
         mOrientation = getOrientation(mContext, uri);
         mOriginalBitmapSmall = loadScaledBitmap(uri, 160);
         if (mOriginalBitmapSmall == null) {
             // Couldn't read the bitmap, let's exit
-            mActivity.cannotLoadImage();
+            mLoadingLock.unlock();
+            return false;
         }
         mOriginalBitmapLarge = loadScaledBitmap(uri, size);
+        if (mOriginalBitmapLarge == null) {
+            mLoadingLock.unlock();
+            return false;
+        }
+        if (MasterImage.getImage().supportsHighRes()) {
+            int highresPreviewSize = mOriginalBitmapLarge.getWidth() * 2;
+            if (highresPreviewSize > mOriginalBounds.width()) {
+                highresPreviewSize = mOriginalBounds.width();
+            }
+            mOriginalBitmapHighres = loadScaledBitmap(uri, highresPreviewSize, false);
+        }
         updateBitmaps();
         mLoadingLock.unlock();
+        return true;
     }
 
     public Uri getUri() {
@@ -125,9 +153,23 @@
 
     public static int getOrientation(Context context, Uri uri) {
         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
-            return getOrientationFromPath(uri.getPath());
+            String mimeType = context.getContentResolver().getType(uri);
+            if (mimeType != ImageLoader.JPEG_MIME_TYPE) {
+                return -1;
+            }
+            String path = uri.getPath();
+            int orientation = -1;
+            InputStream is = null;
+            ExifInterface exif = new ExifInterface();
+            try {
+                exif.readExif(path);
+                orientation = ExifInterface.getRotationForOrientationValue(
+                        exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
+            } catch (IOException e) {
+                Log.w(LOGTAG, "Failed to read EXIF orientation", e);
+            }
+            return orientation;
         }
-
         Cursor cursor = null;
         try {
             cursor = context.getContentResolver().query(uri,
@@ -135,125 +177,120 @@
                         MediaStore.Images.ImageColumns.ORIENTATION
                     },
                     null, null, null);
-            if (cursor.moveToNext()){
-              int ori =   cursor.getInt(0);
+            if (cursor.moveToNext()) {
+                int ori = cursor.getInt(0);
 
-              switch (ori){
-                  case 0:   return ORI_NORMAL;
-                  case 90:  return ORI_ROTATE_90;
-                  case 270: return ORI_ROTATE_270;
-                  case 180: return ORI_ROTATE_180;
-                  default:
-                      return -1;
-              }
-            } else{
+                switch (ori) {
+                    case 0:
+                        return ORI_NORMAL;
+                    case 90:
+                        return ORI_ROTATE_90;
+                    case 270:
+                        return ORI_ROTATE_270;
+                    case 180:
+                        return ORI_ROTATE_180;
+                    default:
+                        return -1;
+                }
+            } else {
                 return -1;
             }
-        } catch (SQLiteException e){
-            return ExifInterface.ORIENTATION_UNDEFINED;
+        } catch (SQLiteException e) {
+            return -1;
         } catch (IllegalArgumentException e) {
-            return ExifInterface.ORIENTATION_UNDEFINED;
+            return -1;
         } finally {
             Utils.closeSilently(cursor);
         }
     }
 
-    static int getOrientationFromPath(String path) {
-        int orientation = -1;
-        try {
-            ExifInterface EXIF = new ExifInterface(path);
-            orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION,
-                    1);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-        return orientation;
-    }
-
     private void updateBitmaps() {
         if (mOrientation > 1) {
             mOriginalBitmapSmall = rotateToPortrait(mOriginalBitmapSmall, mOrientation);
             mOriginalBitmapLarge = rotateToPortrait(mOriginalBitmapLarge, mOrientation);
+            if (mOriginalBitmapHighres != null) {
+                mOriginalBitmapHighres = rotateToPortrait(mOriginalBitmapHighres, mOrientation);
+            }
         }
         mZoomOrientation = mOrientation;
-        mCache.setOriginalBitmap(mOriginalBitmapSmall);
-        mHiresCache.setOriginalBitmap(mOriginalBitmapLarge);
         warnListeners();
     }
 
-    public static Bitmap rotateToPortrait(Bitmap bitmap,int ori) {
-           Matrix matrix = new Matrix();
-           int w = bitmap.getWidth();
-           int h = bitmap.getHeight();
-           if (ori == ORI_ROTATE_90 ||
-                   ori == ORI_ROTATE_270 ||
-                   ori == ORI_TRANSPOSE||
-                   ori == ORI_TRANSVERSE) {
-               int tmp = w;
-               w = h;
-               h = tmp;
-           }
-           switch(ori){
-               case ORI_ROTATE_90:
-                   matrix.setRotate(90,w/2f,h/2f);
-                   break;
-               case ORI_ROTATE_180:
-                   matrix.setRotate(180,w/2f,h/2f);
-                   break;
-               case ORI_ROTATE_270:
-                   matrix.setRotate(270,w/2f,h/2f);
-                   break;
-               case ORI_FLIP_HOR:
-                   matrix.preScale(-1, 1);
-                   break;
-              case ORI_FLIP_VERT:
-                   matrix.preScale(1, -1);
-                   break;
-               case ORI_TRANSPOSE:
-                   matrix.setRotate(90,w/2f,h/2f);
-                   matrix.preScale(1, -1);
-                   break;
-               case ORI_TRANSVERSE:
-                   matrix.setRotate(270,w/2f,h/2f);
-                   matrix.preScale(1, -1);
-                   break;
-               case ORI_NORMAL:
-               default:
-                   return bitmap;
-            }
+    public Bitmap decodeImage(int id, BitmapFactory.Options options) {
+        return BitmapFactory.decodeResource(mContext.getResources(), id, options);
+    }
+
+    public static Bitmap rotateToPortrait(Bitmap bitmap, int ori) {
+        Matrix matrix = new Matrix();
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        if (ori == ORI_ROTATE_90 ||
+                ori == ORI_ROTATE_270 ||
+                ori == ORI_TRANSPOSE ||
+                ori == ORI_TRANSVERSE) {
+            int tmp = w;
+            w = h;
+            h = tmp;
+        }
+        switch (ori) {
+            case ORI_ROTATE_90:
+                matrix.setRotate(90, w / 2f, h / 2f);
+                break;
+            case ORI_ROTATE_180:
+                matrix.setRotate(180, w / 2f, h / 2f);
+                break;
+            case ORI_ROTATE_270:
+                matrix.setRotate(270, w / 2f, h / 2f);
+                break;
+            case ORI_FLIP_HOR:
+                matrix.preScale(-1, 1);
+                break;
+            case ORI_FLIP_VERT:
+                matrix.preScale(1, -1);
+                break;
+            case ORI_TRANSPOSE:
+                matrix.setRotate(90, w / 2f, h / 2f);
+                matrix.preScale(1, -1);
+                break;
+            case ORI_TRANSVERSE:
+                matrix.setRotate(270, w / 2f, h / 2f);
+                matrix.preScale(1, -1);
+                break;
+            case ORI_NORMAL:
+            default:
+                return bitmap;
+        }
 
         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                 bitmap.getHeight(), matrix, true);
     }
 
-    private void closeStream(Closeable stream) {
-        if (stream != null) {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private Bitmap loadRegionBitmap(Uri uri, Rect bounds) {
+    private Bitmap loadRegionBitmap(Uri uri, BitmapFactory.Options options, Rect bounds) {
         InputStream is = null;
         try {
             is = mContext.getContentResolver().openInputStream(uri);
             BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
-            return decoder.decodeRegion(bounds, null);
+            Rect r = new Rect(0, 0, decoder.getWidth(), decoder.getHeight());
+            // return null if bounds are not entirely within the bitmap
+            if (!r.contains(bounds)) {
+                return null;
+            }
+            return decoder.decodeRegion(bounds, options);
         } catch (FileNotFoundException e) {
             Log.e(LOGTAG, "FileNotFoundException: " + uri);
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
-            closeStream(is);
+            Utils.closeSilently(is);
         }
         return null;
     }
 
-    static final int MAX_BITMAP_DIM = 2048;
     private Bitmap loadScaledBitmap(Uri uri, int size) {
+        return loadScaledBitmap(uri, size, true);
+    }
+
+    private Bitmap loadScaledBitmap(Uri uri, int size, boolean enforceSize) {
         InputStream is = null;
         try {
             is = mContext.getContentResolver().openInputStream(uri);
@@ -270,7 +307,12 @@
 
             int scale = 1;
             while (true) {
-                if (width_tmp <= MAX_BITMAP_DIM && height_tmp <= MAX_BITMAP_DIM) {
+                if (width_tmp <= 2 || height_tmp <= 2) {
+                    break;
+                }
+                if (!enforceSize
+                        || (width_tmp <= MAX_BITMAP_DIM
+                        && height_tmp <= MAX_BITMAP_DIM)) {
                     if (width_tmp / 2 < size || height_tmp / 2 < size) {
                         break;
                     }
@@ -283,8 +325,9 @@
             // decode with inSampleSize
             BitmapFactory.Options o2 = new BitmapFactory.Options();
             o2.inSampleSize = scale;
+            o2.inMutable = true;
 
-            closeStream(is);
+            Utils.closeSilently(is);
             is = mContext.getContentResolver().openInputStream(uri);
             return BitmapFactory.decodeStream(is, null, o2);
         } catch (FileNotFoundException e) {
@@ -292,7 +335,7 @@
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
-            closeStream(is);
+            Utils.closeSilently(is);
         }
         return null;
     }
@@ -314,12 +357,15 @@
         return mOriginalBitmapLarge;
     }
 
+    public Bitmap getOriginalBitmapHighres() {
+        return mOriginalBitmapHighres;
+    }
+
     public void addListener(ImageShow imageShow) {
         mLoadingLock.lock();
         if (!mListeners.contains(imageShow)) {
             mListeners.add(imageShow);
         }
-        mHiresCache.addObserver(imageShow);
         mLoadingLock.unlock();
     }
 
@@ -338,73 +384,28 @@
         }
     };
 
-    // TODO: this currently does the loading + filtering on the UI thread -- need to
-    // move this to a background thread.
-    public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds,
-            boolean force) {
+    public Bitmap getScaleOneImageForPreset(Rect bounds, Rect destination) {
         mLoadingLock.lock();
-        Bitmap bmp = mZoomCache.getImage(imagePreset, bounds);
-        if (force || bmp == null) {
-            bmp = loadRegionBitmap(mUri, bounds);
-            if (bmp != null) {
-                // TODO: this workaround for RS might not be needed ultimately
-                Bitmap bmp2 = bmp.copy(Bitmap.Config.ARGB_8888, true);
-                float scaleFactor = imagePreset.getScaleFactor();
-                imagePreset.setScaleFactor(1.0f);
-                bmp2 = imagePreset.apply(bmp2);
-                imagePreset.setScaleFactor(scaleFactor);
-                mZoomCache.setImage(imagePreset, bounds, bmp2);
-                return bmp2;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inMutable = true;
+        if (destination != null) {
+            if (bounds.width() > destination.width()) {
+                int sampleSize = 1;
+                int w = bounds.width();
+                while (w > destination.width()) {
+                    sampleSize *= 2;
+                    w /= sampleSize;
+                }
+                options.inSampleSize = sampleSize;
             }
         }
+        Bitmap bmp = loadRegionBitmap(mUri, options, bounds);
         mLoadingLock.unlock();
         return bmp;
     }
 
-    // Caching method
-    public Bitmap getImageForPreset(ImageShow caller, ImagePreset imagePreset,
-            boolean hiRes) {
-        mLoadingLock.lock();
-        if (mOriginalBitmapSmall == null) {
-            return null;
-        }
-        if (mOriginalBitmapLarge == null) {
-            return null;
-        }
-
-        Bitmap filteredImage = null;
-
-        if (hiRes) {
-            filteredImage = mHiresCache.get(imagePreset);
-        } else {
-            filteredImage = mCache.get(imagePreset);
-        }
-
-        if (filteredImage == null) {
-            if (hiRes) {
-                mHiresCache.prepare(imagePreset);
-                mHiresCache.addObserver(caller);
-            } else {
-                mCache.prepare(imagePreset);
-                mCache.addObserver(caller);
-            }
-        }
-        mLoadingLock.unlock();
-        return filteredImage;
-    }
-
-    public void resetImageForPreset(ImagePreset imagePreset, ImageShow caller) {
-        mLoadingLock.lock();
-        mHiresCache.reset(imagePreset);
-        mCache.reset(imagePreset);
-        mZoomCache.reset(imagePreset);
-        mLoadingLock.unlock();
-    }
-
     public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity,
             File destination) {
-        preset.setIsHighQuality(true);
-        preset.setScaleFactor(1.0f);
         new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() {
 
             @Override
@@ -415,6 +416,211 @@
         }).execute(preset);
     }
 
+    public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        return loadMutableBitmap(context, sourceUri, options);
+    }
+
+    public static Bitmap loadMutableBitmap(Context context, Uri sourceUri,
+            BitmapFactory.Options options) {
+        // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
+        // exist)
+        options.inMutable = true;
+
+        Bitmap bitmap = decodeUriWithBackouts(context, sourceUri, options);
+        if (bitmap == null) {
+            return null;
+        }
+        int orientation = ImageLoader.getOrientation(context, sourceUri);
+        bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
+        return bitmap;
+    }
+
+    public static Bitmap decodeUriWithBackouts(Context context, Uri sourceUri,
+            BitmapFactory.Options options) {
+        boolean noBitmap = true;
+        int num_tries = 0;
+        InputStream is = getInputStream(context, sourceUri);
+
+        if (options.inSampleSize < 1) {
+            options.inSampleSize = 1;
+        }
+        // Stopgap fix for low-memory devices.
+        Bitmap bmap = null;
+        while (noBitmap) {
+            if (is == null) {
+                return null;
+            }
+            try {
+                // Try to decode, downsample if low-memory.
+                bmap = BitmapFactory.decodeStream(is, null, options);
+                noBitmap = false;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+                    throw e;
+                }
+                is = null;
+                bmap = null;
+                System.gc();
+                is = getInputStream(context, sourceUri);
+                options.inSampleSize *= 2;
+            }
+        }
+        Utils.closeSilently(is);
+        return bmap;
+    }
+
+    private static InputStream getInputStream(Context context, Uri sourceUri) {
+        InputStream is = null;
+        try {
+            is = context.getContentResolver().openInputStream(sourceUri);
+        } catch (FileNotFoundException e) {
+            Log.w(LOGTAG, "could not load bitmap ", e);
+            Utils.closeSilently(is);
+            is = null;
+        }
+        return is;
+    }
+
+    public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
+            int id) {
+        boolean noBitmap = true;
+        int num_tries = 0;
+        if (options.inSampleSize < 1) {
+            options.inSampleSize = 1;
+        }
+        // Stopgap fix for low-memory devices.
+        Bitmap bmap = null;
+        while (noBitmap) {
+            try {
+                // Try to decode, downsample if low-memory.
+                bmap = BitmapFactory.decodeResource(
+                        res, id, options);
+                noBitmap = false;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
+                    throw e;
+                }
+                bmap = null;
+                System.gc();
+                options.inSampleSize *= 2;
+            }
+        }
+        return bmap;
+    }
+
+    public void returnFilteredResult(ImagePreset preset,
+            final FilterShowActivity filterShowActivity) {
+        BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() {
+
+            @Override
+            public void onComplete(Bitmap result) {
+                filterShowActivity.onFilteredResult(result);
+            }
+
+            @Override
+            public void onCancel() {
+            }
+
+            @Override
+            public Bitmap onExecute(ImagePreset param) {
+                if (param == null || mUri == null) {
+                    return null;
+                }
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                boolean noBitmap = true;
+                int num_tries = 0;
+                if (options.inSampleSize < 1) {
+                    options.inSampleSize = 1;
+                }
+                Bitmap bitmap = null;
+                // Stopgap fix for low-memory devices.
+                while (noBitmap) {
+                    try {
+                        // Try to do bitmap operations, downsample if low-memory
+                        bitmap = loadMutableBitmap(mContext, mUri, options);
+                        if (bitmap == null) {
+                            Log.w(LOGTAG, "Failed to save image!");
+                            return null;
+                        }
+                        CachingPipeline pipeline = new CachingPipeline(
+                                FiltersManager.getManager(), "Saving");
+                        bitmap = pipeline.renderFinalImage(bitmap, param);
+                        noBitmap = false;
+                    } catch (java.lang.OutOfMemoryError e) {
+                        // Try 5 times before failing for good.
+                        if (++num_tries >= 5) {
+                            throw e;
+                        }
+                        bitmap = null;
+                        System.gc();
+                        options.inSampleSize *= 2;
+                    }
+                }
+                return bitmap;
+            }
+        };
+
+        (new BitmapTask<ImagePreset>(cb)).execute(preset);
+    }
+
+    private String getFileExtension(String requestFormat) {
+        String outputFormat = (requestFormat == null)
+                ? "jpg"
+                : requestFormat;
+        outputFormat = outputFormat.toLowerCase();
+        return (outputFormat.equals("png") || outputFormat.equals("gif"))
+                ? "png" // We don't support gif compression.
+                : "jpg";
+    }
+
+    private CompressFormat convertExtensionToCompressFormat(String extension) {
+        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+    }
+
+    public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat,
+            final FilterShowActivity filterShowActivity) {
+
+        OutputStream out = null;
+        try {
+            out = filterShowActivity.getContentResolver().openOutputStream(uri);
+        } catch (FileNotFoundException e) {
+            Log.w(LOGTAG, "cannot write output", e);
+            out = null;
+        } finally {
+            if (bmap == null || out == null) {
+                return;
+            }
+        }
+
+        final InterruptableOutputStream ios = new InterruptableOutputStream(out);
+
+        BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() {
+
+            @Override
+            public void onComplete(Bitmap result) {
+                filterShowActivity.done();
+            }
+
+            @Override
+            public void onCancel() {
+                ios.interrupt();
+            }
+
+            @Override
+            public Bitmap onExecute(Bitmap param) {
+                CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat));
+                param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios);
+                Utils.closeSilently(ios);
+                return null;
+            }
+        };
+
+        (new BitmapTask<Bitmap>(cb)).execute(bmap);
+    }
+
     public void setAdapter(HistoryAdapter adapter) {
         mAdapter = adapter;
     }
@@ -438,41 +644,41 @@
      * @return true if it is a light Cycle image that is full 360
      */
     public boolean queryLightCycle360() {
+        InputStream is = null;
         try {
-            InputStream is = mContext.getContentResolver().openInputStream(getUri());
+            is = mContext.getContentResolver().openInputStream(getUri());
             XMPMeta meta = XmpUtilHelper.extractXMPMeta(is);
             if (meta == null) {
                 return false;
             }
             String name = meta.getPacketHeader();
-            try {
-                String namespace = "http://ns.google.com/photos/1.0/panorama/";
-                String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
-                String fullWidthName = "GPano:FullPanoWidthPixels";
+            String namespace = "http://ns.google.com/photos/1.0/panorama/";
+            String cropWidthName = "GPano:CroppedAreaImageWidthPixels";
+            String fullWidthName = "GPano:FullPanoWidthPixels";
 
-                if (!meta.doesPropertyExist(namespace, cropWidthName)) {
-                    return false;
-                }
-                if (!meta.doesPropertyExist(namespace, fullWidthName)) {
-                    return false;
-                }
-
-                Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
-                Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
-
-                // Definition of a 360:
-                // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
-                if (cropValue != null && fullValue != null) {
-                    return cropValue.equals(fullValue);
-                }
-
-                return false;
-            } catch (XMPException e) {
+            if (!meta.doesPropertyExist(namespace, cropWidthName)) {
                 return false;
             }
+            if (!meta.doesPropertyExist(namespace, fullWidthName)) {
+                return false;
+            }
+
+            Integer cropValue = meta.getPropertyInteger(namespace, cropWidthName);
+            Integer fullValue = meta.getPropertyInteger(namespace, fullWidthName);
+
+            // Definition of a 360:
+            // GFullPanoWidthPixels == CroppedAreaImageWidthPixels
+            if (cropValue != null && fullValue != null) {
+                return cropValue.equals(fullValue);
+            }
+
+            return false;
         } catch (FileNotFoundException e) {
             return false;
+        } catch (XMPException e) {
+            return false;
+        } finally {
+            Utils.closeSilently(is);
         }
     }
-
 }
diff --git a/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
new file mode 100644
index 0000000..e5bc411
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/RenderingRequest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import com.android.gallery3d.app.Log;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.FilterEnvironment;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class RenderingRequest {
+    private static final String LOGTAG = "RenderingRequest";
+    private boolean mIsDirect = false;
+    private Bitmap mBitmap = null;
+    private ImagePreset mImagePreset = null;
+    private ImagePreset mOriginalImagePreset = null;
+    private RenderingRequestCaller mCaller = null;
+    private float mScaleFactor = 1.0f;
+    private Rect mBounds = null;
+    private Rect mDestination = null;
+    private int mType = FULL_RENDERING;
+    public static final int FULL_RENDERING = 0;
+    public static final int FILTERS_RENDERING = 1;
+    public static final int GEOMETRY_RENDERING = 2;
+    public static final int ICON_RENDERING = 3;
+    public static final int PARTIAL_RENDERING = 4;
+    public static final int HIGHRES_RENDERING = 5;
+    public static final int STYLE_ICON_RENDERING = 6;
+
+    private static final Bitmap.Config mConfig = Bitmap.Config.ARGB_8888;
+
+    public static void post(Bitmap source, ImagePreset preset, int type, RenderingRequestCaller caller) {
+        RenderingRequest.post(source, preset, type, caller, null, null);
+    }
+
+    public static void post(Bitmap source, ImagePreset preset, int type,
+                            RenderingRequestCaller caller, Rect bounds, Rect destination) {
+        if (((type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) && source == null)
+                || preset == null || caller == null) {
+            Log.v(LOGTAG, "something null: source: " + source
+                    + " or preset: " + preset + " or caller: " + caller);
+            return;
+        }
+        RenderingRequest request = new RenderingRequest();
+        Bitmap bitmap = null;
+        if (type == FULL_RENDERING
+                || type == GEOMETRY_RENDERING
+                || type == ICON_RENDERING
+                || type == STYLE_ICON_RENDERING) {
+            CachingPipeline pipeline = new CachingPipeline(
+                    FiltersManager.getManager(), "Icon");
+            bitmap = pipeline.renderGeometryIcon(source, preset);
+        } else if (type != PARTIAL_RENDERING && type != HIGHRES_RENDERING) {
+            bitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), mConfig);
+        }
+
+        request.setBitmap(bitmap);
+        ImagePreset passedPreset = new ImagePreset(preset);
+        passedPreset.setImageLoader(MasterImage.getImage().getImageLoader());
+        request.setOriginalImagePreset(preset);
+        request.setScaleFactor(MasterImage.getImage().getScaleFactor());
+
+        if (type == PARTIAL_RENDERING) {
+            request.setBounds(bounds);
+            request.setDestination(destination);
+            passedPreset.setPartialRendering(true, bounds);
+        }
+
+        request.setImagePreset(passedPreset);
+        request.setType(type);
+        request.setCaller(caller);
+        request.post();
+    }
+
+    public void post() {
+        FilteringPipeline.getPipeline().postRenderingRequest(this);
+    }
+
+    public void markAvailable() {
+        if (mBitmap == null || mImagePreset == null
+                || mCaller == null) {
+            return;
+        }
+        mCaller.available(this);
+    }
+
+    public boolean isDirect() {
+        return mIsDirect;
+    }
+
+    public void setDirect(boolean isDirect) {
+        mIsDirect = isDirect;
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        mBitmap = bitmap;
+    }
+
+    public ImagePreset getImagePreset() {
+        return mImagePreset;
+    }
+
+    public void setImagePreset(ImagePreset imagePreset) {
+        mImagePreset = imagePreset;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public void setType(int type) {
+        mType = type;
+    }
+
+    public void setCaller(RenderingRequestCaller caller) {
+        mCaller = caller;
+    }
+
+    public Rect getBounds() {
+        return mBounds;
+    }
+
+    public void setBounds(Rect bounds) {
+        mBounds = bounds;
+    }
+
+    public void setScaleFactor(float scaleFactor) {
+        mScaleFactor = scaleFactor;
+    }
+
+    public float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    public Rect getDestination() {
+        return mDestination;
+    }
+
+    public void setDestination(Rect destination) {
+        mDestination = destination;
+    }
+
+    public ImagePreset getOriginalImagePreset() {
+        return mOriginalImagePreset;
+    }
+
+    public void setOriginalImagePreset(ImagePreset originalImagePreset) {
+        mOriginalImagePreset = originalImagePreset;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/RenderingRequestCaller.java b/src/com/android/gallery3d/filtershow/cache/RenderingRequestCaller.java
new file mode 100644
index 0000000..240eb8f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/RenderingRequestCaller.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.cache;
+
+public interface RenderingRequestCaller {
+    public void available(RenderingRequest request);
+}
diff --git a/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java b/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java
new file mode 100644
index 0000000..ba7b769
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/cache/TripleBufferBitmap.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gallery3d.filtershow.cache;
+
+import android.graphics.Bitmap;
+
+public class TripleBufferBitmap {
+
+    private static String LOGTAG = "TripleBufferBitmap";
+
+    private volatile Bitmap mBitmaps[] = new Bitmap[3];
+    private volatile Bitmap mProducer = null;
+    private volatile Bitmap mConsumer = null;
+    private volatile Bitmap mIntermediate = null;
+    private volatile boolean mNeedsSwap = false;
+
+    private final Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888;
+    private volatile boolean mNeedsRepaint = true;
+
+    public TripleBufferBitmap() {
+
+    }
+
+    public synchronized void updateBitmaps(Bitmap bitmap) {
+        mBitmaps[0] = bitmap.copy(mBitmapConfig, true);
+        mBitmaps[1] = bitmap.copy(mBitmapConfig, true);
+        mBitmaps[2] = bitmap.copy(mBitmapConfig, true);
+        mProducer = mBitmaps[0];
+        mConsumer = mBitmaps[1];
+        mIntermediate = mBitmaps[2];
+    }
+
+    public synchronized void updateProducerBitmap(Bitmap bitmap) {
+        mProducer = bitmap.copy(mBitmapConfig, true);
+    }
+
+    public synchronized void setProducer(Bitmap producer) {
+        mProducer = producer;
+    }
+
+    public synchronized Bitmap getProducer() {
+        return mProducer;
+    }
+
+    public synchronized Bitmap getConsumer() {
+        return mConsumer;
+    }
+
+    public synchronized void swapProducer() {
+        Bitmap intermediate = mIntermediate;
+        mIntermediate = mProducer;
+        mProducer = intermediate;
+        mNeedsSwap = true;
+    }
+
+    public synchronized void swapConsumer() {
+        if (!mNeedsSwap) {
+            return;
+        }
+        Bitmap intermediate = mIntermediate;
+        mIntermediate = mConsumer;
+        mConsumer = intermediate;
+        mNeedsSwap = false;
+    }
+
+    public synchronized void invalidate() {
+        mNeedsRepaint = true;
+    }
+
+    public synchronized boolean checkRepaintNeeded() {
+        if (mNeedsRepaint) {
+            mNeedsRepaint = false;
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/category/Action.java b/src/com/android/gallery3d/filtershow/category/Action.java
new file mode 100644
index 0000000..4f2c128
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/Action.java
@@ -0,0 +1,182 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import com.android.gallery3d.filtershow.cache.RenderingRequest;
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+public class Action implements RenderingRequestCaller {
+
+    private static final String LOGTAG = "Action";
+    private FilterRepresentation mRepresentation;
+    private String mName;
+    private Rect mImageFrame;
+    private Bitmap mImage;
+    private CategoryAdapter mAdapter;
+    public static final int FULL_VIEW = 0;
+    public static final int CROP_VIEW = 1;
+    private int mType = CROP_VIEW;
+    private Bitmap mPortraitImage;
+    private Bitmap mOverlayBitmap;
+    private Context mContext;
+
+    public Action(Context context, FilterRepresentation representation, int type) {
+        mContext = context;
+        setRepresentation(representation);
+        setType(type);
+    }
+
+    public Action(Context context, FilterRepresentation representation) {
+        this(context, representation, CROP_VIEW);
+    }
+
+    public FilterRepresentation getRepresentation() {
+        return mRepresentation;
+    }
+
+    public void setRepresentation(FilterRepresentation representation) {
+        mRepresentation = representation;
+        mName = representation.getName();
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public void setImageFrame(Rect imageFrame) {
+        if (mImageFrame != null && mImageFrame.equals(imageFrame)) {
+            return;
+        }
+        Bitmap bitmap = MasterImage.getImage().getLargeThumbnailBitmap();
+        if (bitmap != null) {
+            mImageFrame = imageFrame;
+            int w = mImageFrame.width();
+            int h = mImageFrame.height();
+            if (mType == CROP_VIEW) {
+                w /= 2;
+            }
+            Bitmap bitmapCrop = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+            drawCenteredImage(bitmap, bitmapCrop, true);
+
+            postNewIconRenderRequest(bitmapCrop);
+        }
+    }
+
+    public Bitmap getImage() {
+        return mImage;
+    }
+
+    public void setImage(Bitmap image) {
+        mImage = image;
+    }
+
+    public void setAdapter(CategoryAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    public void setType(int type) {
+        mType = type;
+    }
+
+    private void postNewIconRenderRequest(Bitmap bitmap) {
+        if (bitmap != null && mRepresentation != null) {
+            ImagePreset preset = new ImagePreset();
+            preset.addFilter(mRepresentation);
+            RenderingRequest.post(bitmap,
+                    preset, RenderingRequest.ICON_RENDERING, this);
+        }
+    }
+
+    private void drawCenteredImage(Bitmap source, Bitmap destination, boolean scale) {
+        RectF image = new RectF(0, 0, source.getWidth(), source.getHeight());
+        int border = 0;
+        if (!scale) {
+            border = destination.getWidth() - destination.getHeight();
+            if (border < 0) {
+                border = 0;
+            }
+        }
+        RectF frame = new RectF(border, 0,
+                destination.getWidth() - border,
+                destination.getHeight());
+        Matrix m = new Matrix();
+        m.setRectToRect(frame, image, Matrix.ScaleToFit.CENTER);
+        image.set(frame);
+        m.mapRect(image);
+        m.setRectToRect(image, frame, Matrix.ScaleToFit.FILL);
+        Canvas canvas = new Canvas(destination);
+        canvas.drawBitmap(source, m, new Paint());
+    }
+
+    @Override
+    public void available(RenderingRequest request) {
+        mImage = request.getBitmap();
+        if (mImage == null) {
+            return;
+        }
+        if (mRepresentation.getOverlayId() != 0 && mOverlayBitmap == null) {
+            mOverlayBitmap = BitmapFactory.decodeResource(
+                    mContext.getResources(),
+                    mRepresentation.getOverlayId());
+        }
+        if (mOverlayBitmap != null) {
+            if (getRepresentation().getPriority() == FilterRepresentation.TYPE_BORDER) {
+                Canvas canvas = new Canvas(mImage);
+                canvas.drawBitmap(mOverlayBitmap, new Rect(0, 0, mOverlayBitmap.getWidth(), mOverlayBitmap.getHeight()),
+                        new Rect(0, 0, mImage.getWidth(), mImage.getHeight()), new Paint());
+            } else {
+                Canvas canvas = new Canvas(mImage);
+                canvas.drawARGB(128, 0, 0, 0);
+                drawCenteredImage(mOverlayBitmap, mImage, false);
+            }
+        }
+        if (mAdapter != null) {
+            mAdapter.notifyDataSetChanged();
+        }
+    }
+
+    public void setPortraitImage(Bitmap portraitImage) {
+        mPortraitImage = portraitImage;
+    }
+
+    public Bitmap getPortraitImage() {
+        return mPortraitImage;
+    }
+
+    public Bitmap getOverlayBitmap() {
+        return mOverlayBitmap;
+    }
+
+    public void setOverlayBitmap(Bitmap overlayBitmap) {
+        mOverlayBitmap = overlayBitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java
new file mode 100644
index 0000000..cfa64bc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryAdapter.java
@@ -0,0 +1,180 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.ui.FilterIconButton;
+
+public class CategoryAdapter extends ArrayAdapter<Action> {
+
+    private static final String LOGTAG = "CategoryAdapter";
+    private int mItemHeight;
+    private View mContainer;
+    private int mItemWidth = ListView.LayoutParams.MATCH_PARENT;
+    private boolean mUseFilterIconButton = false;
+    private int mSelectedPosition;
+    int mCategory;
+
+    public CategoryAdapter(Context context, int textViewResourceId) {
+        super(context, textViewResourceId);
+        mItemHeight = (int) (context.getResources().getDisplayMetrics().density * 100);
+    }
+
+    public CategoryAdapter(Context context) {
+        this(context, 0);
+    }
+
+    public void setItemHeight(int height) {
+        mItemHeight = height;
+    }
+
+    public void setItemWidth(int width) {
+        mItemWidth = width;
+    }
+
+    @Override
+    public void add(Action action) {
+        super.add(action);
+        action.setAdapter(this);
+    }
+
+    public void initializeSelection(int category) {
+        mCategory = category;
+        if (category == MainPanel.LOOKS || category == MainPanel.BORDERS) {
+            ImagePreset preset = MasterImage.getImage().getPreset();
+            if (preset != null) {
+                for (int i = 0; i < getCount(); i++) {
+                    if (preset.historyName().equals(getItem(i).getRepresentation().getName())) {
+                        mSelectedPosition = i;
+                    }
+                }
+            }
+        } else {
+            mSelectedPosition = -1;
+        }
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        if (mUseFilterIconButton) {
+            if (convertView == null) {
+                LayoutInflater inflater =
+                        (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+                convertView = inflater.inflate(R.layout.filtericonbutton, parent, false);
+            }
+            FilterIconButton view = (FilterIconButton) convertView;
+            Action action = getItem(position);
+            view.setAction(action);
+            view.setup(action.getName(), null, this);
+            view.setLayoutParams(
+                    new ListView.LayoutParams(mItemWidth, mItemHeight));
+            view.setTag(position);
+            if (mCategory == MainPanel.LOOKS || mCategory == MainPanel.BORDERS) {
+                view.setBackgroundResource(0);
+            }
+            return view;
+        }
+        if (convertView == null) {
+            convertView = new CategoryView(getContext());
+        }
+        CategoryView view = (CategoryView) convertView;
+        view.setAction(getItem(position), this);
+        view.setLayoutParams(
+                new ListView.LayoutParams(mItemWidth, mItemHeight));
+        view.setTag(position);
+        return view;
+    }
+
+    public void setSelected(View v) {
+        int old = mSelectedPosition;
+        mSelectedPosition = (Integer) v.getTag();
+        if (old != -1) {
+            invalidateView(old);
+        }
+        invalidateView(mSelectedPosition);
+    }
+
+    public boolean isSelected(View v) {
+        return (Integer) v.getTag() == mSelectedPosition;
+    }
+
+    private void invalidateView(int position) {
+        View child = null;
+        if (mContainer instanceof ListView) {
+            ListView lv = (ListView) mContainer;
+            child = lv.getChildAt(position - lv.getFirstVisiblePosition());
+        } else {
+            CategoryTrack ct = (CategoryTrack) mContainer;
+            child = ct.getChildAt(position);
+        }
+        if (child != null) {
+            child.invalidate();
+        }
+    }
+
+    public void setContainer(View container) {
+        mContainer = container;
+    }
+
+    public void imageLoaded() {
+        notifyDataSetChanged();
+    }
+
+    public void setUseFilterIconButton(boolean useFilterIconButton) {
+        mUseFilterIconButton = useFilterIconButton;
+    }
+
+    public boolean isUseFilterIconButton() {
+        return mUseFilterIconButton;
+    }
+
+    public FilterRepresentation getTinyPlanet() {
+        for (int i = 0; i < getCount(); i++) {
+            Action action = getItem(i);
+            if (action.getRepresentation() != null
+                    && action.getRepresentation().getFilterClass()
+                    == ImageFilterTinyPlanet.class) {
+                return action.getRepresentation();
+            }
+        }
+        return null;
+    }
+
+    public void removeTinyPlanet() {
+        for (int i = 0; i < getCount(); i++) {
+            Action action = getItem(i);
+            if (action.getRepresentation() != null
+                    && action.getRepresentation().getFilterClass()
+                    == ImageFilterTinyPlanet.class) {
+                remove(action);
+                return;
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryPanel.java b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java
new file mode 100644
index 0000000..abae80f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryPanel.java
@@ -0,0 +1,106 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+
+public class CategoryPanel extends Fragment {
+
+    public static final String FRAGMENT_TAG = "CategoryPanel";
+    private static final String PARAMETER_TAG = "currentPanel";
+
+    private int mCurrentAdapter = MainPanel.LOOKS;
+    private CategoryAdapter mAdapter;
+
+    public void setAdapter(int value) {
+        mCurrentAdapter = value;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        loadAdapter(mCurrentAdapter);
+    }
+
+    private void loadAdapter(int adapter) {
+        FilterShowActivity activity = (FilterShowActivity) getActivity();
+        switch (adapter) {
+            case MainPanel.LOOKS: {
+                mAdapter = activity.getCategoryLooksAdapter();
+                mAdapter.initializeSelection(MainPanel.LOOKS);
+                break;
+            }
+            case MainPanel.BORDERS: {
+                mAdapter = activity.getCategoryBordersAdapter();
+                mAdapter.initializeSelection(MainPanel.BORDERS);
+                break;
+            }
+            case MainPanel.GEOMETRY: {
+                mAdapter = activity.getCategoryGeometryAdapter();
+                mAdapter.initializeSelection(MainPanel.GEOMETRY);
+                break;
+            }
+            case MainPanel.FILTERS: {
+                mAdapter = activity.getCategoryFiltersAdapter();
+                mAdapter.initializeSelection(MainPanel.FILTERS);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle state) {
+        super.onSaveInstanceState(state);
+        state.putInt(PARAMETER_TAG, mCurrentAdapter);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        LinearLayout main = (LinearLayout) inflater.inflate(
+                R.layout.filtershow_category_panel_new, container,
+                false);
+
+        if (savedInstanceState != null) {
+            int selectedPanel = savedInstanceState.getInt(PARAMETER_TAG);
+            loadAdapter(selectedPanel);
+        }
+
+        View panelView = main.findViewById(R.id.listItems);
+        if (panelView instanceof CategoryTrack) {
+            CategoryTrack panel = (CategoryTrack) panelView;
+            mAdapter.setUseFilterIconButton(true);
+            panel.setAdapter(mAdapter);
+            mAdapter.setContainer(panel);
+        } else {
+            ListView panel = (ListView) main.findViewById(R.id.listItems);
+            panel.setAdapter(mAdapter);
+            mAdapter.setContainer(panel);
+        }
+        return main;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryTrack.java b/src/com/android/gallery3d/filtershow/category/CategoryTrack.java
new file mode 100644
index 0000000..e0a8a2f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryTrack.java
@@ -0,0 +1,54 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+
+public class CategoryTrack extends LinearLayout {
+
+    private CategoryAdapter mAdapter;
+    private int mElemSize;
+
+    public CategoryTrack(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CategoryTrack);
+        mElemSize = a.getDimensionPixelSize(R.styleable.CategoryTrack_iconSize, 0);
+    }
+
+    public void setAdapter(CategoryAdapter adapter) {
+        mAdapter = adapter;
+        mAdapter.setItemWidth(mElemSize);
+        mAdapter.setItemHeight(LayoutParams.MATCH_PARENT);
+        fillContent();
+    }
+
+    public void fillContent() {
+        removeAllViews();
+        int n = mAdapter.getCount();
+        for (int i = 0; i < n; i++) {
+            View view = mAdapter.getView(i, null, this);
+            addView(view, i);
+        }
+        requestLayout();
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/category/CategoryView.java b/src/com/android/gallery3d/filtershow/category/CategoryView.java
new file mode 100644
index 0000000..059eb10
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/CategoryView.java
@@ -0,0 +1,132 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.util.Log;
+import android.view.View;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.ui.SelectionRenderer;
+
+public class CategoryView extends View implements View.OnClickListener {
+
+    private static final String LOGTAG = "CategoryView";
+    private Paint mPaint = new Paint();
+    private Action mAction;
+    private Rect mTextBounds = new Rect();
+    private static int sMargin = 16;
+    private static int sTextSize = 32;
+    private int mTextColor;
+    private int mBackgroundColor;
+    private Paint mSelectPaint;
+    CategoryAdapter mAdapter;
+    private int mSelectionStroke;
+    private Paint mBorderPaint;
+    private int mBorderStroke;
+
+    public static void setTextSize(int size) {
+        sTextSize = size;
+    }
+
+    public static void setMargin(int margin) {
+        sMargin = margin;
+    }
+
+    public CategoryView(Context context) {
+        super(context);
+        setOnClickListener(this);
+        Resources res = getResources();
+        mBackgroundColor = res.getColor(R.color.filtershow_categoryview_background);
+        mTextColor = res.getColor(R.color.filtershow_categoryview_text);
+        mSelectionStroke = res.getDimensionPixelSize(R.dimen.thumbnail_margin);
+        mSelectPaint = new Paint();
+        mSelectPaint.setStyle(Paint.Style.FILL);
+        mSelectPaint.setColor(res.getColor(R.color.filtershow_category_selection));
+        mBorderPaint = new Paint(mSelectPaint);
+        mBorderPaint.setColor(Color.BLACK);
+        mBorderStroke = mSelectionStroke / 3;
+    }
+
+    public void drawText(Canvas canvas, String text) {
+        if (text == null) {
+            return;
+        }
+        text = text.toUpperCase();
+        mPaint.setTextSize(sTextSize);
+        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
+        float textWidth = mPaint.measureText(text);
+        mPaint.getTextBounds(text, 0, text.length(), mTextBounds);
+        int x = (int) (canvas.getWidth() - textWidth - sMargin);
+        int y = canvas.getHeight() - sMargin;
+        canvas.drawText(text, x, y, mPaint);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        canvas.drawColor(mBackgroundColor);
+        if (mAction != null) {
+            mPaint.reset();
+            mPaint.setAntiAlias(true);
+            if (mAction.getImage() == null) {
+                mAction.setImageFrame(new Rect(0, 0, canvas.getWidth(), canvas.getHeight()));
+            } else {
+                Bitmap bitmap = mAction.getImage();
+                canvas.drawBitmap(bitmap, 0, 0, mPaint);
+                if (mAdapter.isSelected(this)) {
+                    SelectionRenderer.drawSelection(canvas, 0, 0,
+                            Math.min(bitmap.getWidth(), getWidth()),
+                            Math.min(bitmap.getHeight(), getHeight()),
+                            mSelectionStroke, mSelectPaint, mBorderStroke, mBorderPaint);
+                }
+            }
+            mPaint.setColor(mBackgroundColor);
+            mPaint.setStyle(Paint.Style.STROKE);
+            mPaint.setStrokeWidth(3);
+            drawText(canvas, mAction.getName());
+            mPaint.setColor(mTextColor);
+            mPaint.setStyle(Paint.Style.FILL);
+            mPaint.setStrokeWidth(1);
+            drawText(canvas, mAction.getName());
+        }
+    }
+
+    public void setAction(Action action, CategoryAdapter adapter) {
+        mAction = action;
+        mAdapter = adapter;
+        invalidate();
+    }
+
+    public FilterRepresentation getRepresentation() {
+        return mAction.getRepresentation();
+    }
+
+    @Override
+    public void onClick(View view) {
+        FilterShowActivity activity = (FilterShowActivity) getContext();
+        activity.showRepresentation(mAction.getRepresentation());
+        mAdapter.setSelected(this);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/category/MainPanel.java b/src/com/android/gallery3d/filtershow/category/MainPanel.java
new file mode 100644
index 0000000..9a64ffb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/category/MainPanel.java
@@ -0,0 +1,239 @@
+/*
+ * 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.gallery3d.filtershow.category;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.state.StatePanel;
+
+public class MainPanel extends Fragment {
+
+    private static final String LOGTAG = "MainPanel";
+
+    private LinearLayout mMainView;
+    private ImageButton looksButton;
+    private ImageButton bordersButton;
+    private ImageButton geometryButton;
+    private ImageButton filtersButton;
+
+    public static final String FRAGMENT_TAG = "MainPanel";
+    public static final int LOOKS = 0;
+    public static final int BORDERS = 1;
+    public static final int GEOMETRY = 2;
+    public static final int FILTERS = 3;
+
+    private int mCurrentSelected = -1;
+
+    private void selection(int position, boolean value) {
+        if (value) {
+            FilterShowActivity activity = (FilterShowActivity) getActivity();
+            activity.setCurrentPanel(position);
+        }
+        switch (position) {
+            case LOOKS: {
+                looksButton.setSelected(value);
+                break;
+            }
+            case BORDERS: {
+                bordersButton.setSelected(value);
+                break;
+            }
+            case GEOMETRY: {
+                geometryButton.setSelected(value);
+                break;
+            }
+            case FILTERS: {
+                filtersButton.setSelected(value);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        if (mMainView != null) {
+            if (mMainView.getParent() != null) {
+                ViewGroup parent = (ViewGroup) mMainView.getParent();
+                parent.removeView(mMainView);
+            }
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+
+        mMainView = (LinearLayout) inflater.inflate(
+                R.layout.filtershow_main_panel, null, false);
+
+        looksButton = (ImageButton) mMainView.findViewById(R.id.fxButton);
+        bordersButton = (ImageButton) mMainView.findViewById(R.id.borderButton);
+        geometryButton = (ImageButton) mMainView.findViewById(R.id.geometryButton);
+        filtersButton = (ImageButton) mMainView.findViewById(R.id.colorsButton);
+
+        looksButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showPanel(LOOKS);
+            }
+        });
+        bordersButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showPanel(BORDERS);
+            }
+        });
+        geometryButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showPanel(GEOMETRY);
+            }
+        });
+        filtersButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showPanel(FILTERS);
+            }
+        });
+
+        FilterShowActivity activity = (FilterShowActivity) getActivity();
+        showImageStatePanel(activity.isShowingImageStatePanel());
+        showPanel(activity.getCurrentPanel());
+        return mMainView;
+    }
+
+    private boolean isRightAnimation(int newPos) {
+        if (newPos < mCurrentSelected) {
+            return false;
+        }
+        return true;
+    }
+
+    private void setCategoryFragment(CategoryPanel category, boolean fromRight) {
+        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+        if (fromRight) {
+            transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_right);
+        } else {
+            transaction.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_left);
+        }
+        transaction.replace(R.id.category_panel_container, category, CategoryPanel.FRAGMENT_TAG);
+        transaction.commit();
+    }
+
+    public void loadCategoryLookPanel() {
+        if (mCurrentSelected == LOOKS) {
+            return;
+        }
+        boolean fromRight = isRightAnimation(LOOKS);
+        selection(mCurrentSelected, false);
+        CategoryPanel categoryPanel = new CategoryPanel();
+        categoryPanel.setAdapter(LOOKS);
+        setCategoryFragment(categoryPanel, fromRight);
+        mCurrentSelected = LOOKS;
+        selection(mCurrentSelected, true);
+    }
+
+    public void loadCategoryBorderPanel() {
+        if (mCurrentSelected == BORDERS) {
+            return;
+        }
+        boolean fromRight = isRightAnimation(BORDERS);
+        selection(mCurrentSelected, false);
+        CategoryPanel categoryPanel = new CategoryPanel();
+        categoryPanel.setAdapter(BORDERS);
+        setCategoryFragment(categoryPanel, fromRight);
+        mCurrentSelected = BORDERS;
+        selection(mCurrentSelected, true);
+    }
+
+    public void loadCategoryGeometryPanel() {
+        if (mCurrentSelected == GEOMETRY) {
+            return;
+        }
+        boolean fromRight = isRightAnimation(GEOMETRY);
+        selection(mCurrentSelected, false);
+        CategoryPanel categoryPanel = new CategoryPanel();
+        categoryPanel.setAdapter(GEOMETRY);
+        setCategoryFragment(categoryPanel, fromRight);
+        mCurrentSelected = GEOMETRY;
+        selection(mCurrentSelected, true);
+    }
+
+    public void loadCategoryFiltersPanel() {
+        if (mCurrentSelected == FILTERS) {
+            return;
+        }
+        boolean fromRight = isRightAnimation(FILTERS);
+        selection(mCurrentSelected, false);
+        CategoryPanel categoryPanel = new CategoryPanel();
+        categoryPanel.setAdapter(FILTERS);
+        setCategoryFragment(categoryPanel, fromRight);
+        mCurrentSelected = FILTERS;
+        selection(mCurrentSelected, true);
+    }
+
+    public void showPanel(int currentPanel) {
+        switch (currentPanel) {
+            case LOOKS: {
+                loadCategoryLookPanel();
+                break;
+            }
+            case BORDERS: {
+                loadCategoryBorderPanel();
+                break;
+            }
+            case GEOMETRY: {
+                loadCategoryGeometryPanel();
+                break;
+            }
+            case FILTERS: {
+                loadCategoryFiltersPanel();
+                break;
+            }
+        }
+    }
+
+    public void showImageStatePanel(boolean show) {
+        if (mMainView.findViewById(R.id.state_panel_container) == null) {
+            return;
+        }
+        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+        final View container = mMainView.findViewById(R.id.state_panel_container);
+        if (show) {
+            container.setVisibility(View.VISIBLE);
+            StatePanel statePanel = new StatePanel();
+            transaction.replace(R.id.state_panel_container, statePanel, StatePanel.FRAGMENT_TAG);
+        } else {
+            container.setVisibility(View.GONE);
+            Fragment statePanel = getChildFragmentManager().findFragmentByTag(StatePanel.FRAGMENT_TAG);
+            if (statePanel != null) {
+                transaction.remove(statePanel);
+            }
+        }
+        transaction.commit();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
new file mode 100644
index 0000000..dd4df7d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorGridDialog.java
@@ -0,0 +1,100 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorGridDialog extends Dialog {
+    RGBListener mCallback;
+    private static final String LOGTAG = "ColorGridDialog";
+
+    public ColorGridDialog(Context context, final RGBListener cl) {
+        super(context);
+        mCallback = cl;
+        setTitle(R.string.color_pick_title);
+        setContentView(R.layout.filtershow_color_gird);
+        Button sel = (Button) findViewById(R.id.filtershow_cp_custom);
+        ArrayList<Button> b = getButtons((ViewGroup) getWindow().getDecorView());
+        int k = 0;
+        float[] hsv = new float[3];
+
+        for (Button button : b) {
+            if (!button.equals(sel)){
+                hsv[0] = (k % 5) * 360 / 5;
+                hsv[1] = (k / 5) / 3.0f;
+                hsv[2] = (k < 5) ? (k / 4f) : 1;
+                final int c = (Color.HSVToColor(hsv) & 0x00FFFFFF) | 0xAA000000;
+                GradientDrawable sd = ((GradientDrawable) button.getBackground());
+                button.setOnClickListener(new View.OnClickListener() {
+                        @Override
+                    public void onClick(View v) {
+                        mCallback.setColor(c);
+                        dismiss();
+                    }
+                });
+                sd.setColor(c);
+                k++;
+            }
+
+        }
+        sel.setOnClickListener(new View.OnClickListener() {
+                @Override
+            public void onClick(View v) {
+                showColorPicker();
+                ColorGridDialog.this.dismiss();
+            }
+        });
+    }
+
+    private ArrayList<Button> getButtons(ViewGroup vg) {
+        ArrayList<Button> list = new ArrayList<Button>();
+        for (int i = 0; i < vg.getChildCount(); i++) {
+            View v = vg.getChildAt(i);
+            if (v instanceof Button) {
+                list.add((Button) v);
+            } else if (v instanceof ViewGroup) {
+                list.addAll(getButtons((ViewGroup) v));
+            }
+        }
+        return list;
+    }
+
+    public void showColorPicker() {
+        ColorListener cl = new ColorListener() {
+                @Override
+            public void setColor(float[] hsvo) {
+                int c = Color.HSVToColor(hsvo) & 0xFFFFFF;
+                int alpha = (int) (hsvo[3] * 255);
+                c |= alpha << 24;
+                mCallback.setColor(c);
+            }
+        };
+        ColorPickerDialog cpd = new ColorPickerDialog(this.getContext(), cl);
+        cpd.show();
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java
new file mode 100644
index 0000000..5127dad
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+public interface ColorListener {
+    void setColor(float[] hsvo);
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java
new file mode 100644
index 0000000..2bff501
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorOpacityView.java
@@ -0,0 +1,197 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorOpacityView extends View implements ColorListener {
+
+    private float mRadius;
+    private float mWidth;
+    private Paint mBarPaint1;
+    private Paint mLinePaint1;
+    private Paint mLinePaint2;
+    private Paint mCheckPaint;
+
+    private float mHeight;
+    private Paint mDotPaint;
+    private int mBgcolor = 0;
+
+    private float mDotRadius;
+    private float mBorder;
+
+    private float[] mHSVO = new float[4];
+    private int mSliderColor;
+    private float mDotX = mBorder;
+    private float mDotY = mBorder;
+    private final static float DOT_SIZE = ColorRectView.DOT_SIZE;
+    public final static float BORDER_SIZE = 20;;
+
+    public ColorOpacityView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+        DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+        float mDpToPix = metrics.density;
+        mDotRadius = DOT_SIZE * mDpToPix;
+        mBorder = BORDER_SIZE * mDpToPix;
+        mBarPaint1 = new Paint();
+
+        mDotPaint = new Paint();
+
+        mDotPaint.setStyle(Paint.Style.FILL);
+        mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+        mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+
+        mBarPaint1.setStyle(Paint.Style.FILL);
+
+        mLinePaint1 = new Paint();
+        mLinePaint1.setColor(Color.GRAY);
+        mLinePaint2 = new Paint();
+        mLinePaint2.setColor(mSliderColor);
+        mLinePaint2.setStrokeWidth(4);
+
+        int[] colors = new int[16 * 16];
+        for (int i = 0; i < colors.length; i++) {
+            int y = i / (16 * 8);
+            int x = (i / 8) % 2;
+            colors[i] = (x == y) ? 0xFFAAAAAA : 0xFF444444;
+        }
+        Bitmap bitmap = Bitmap.createBitmap(colors, 16, 16, Bitmap.Config.ARGB_8888);
+        BitmapShader bs = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
+        mCheckPaint = new Paint();
+        mCheckPaint.setShader(bs);
+    }
+
+    public boolean onDown(MotionEvent e) {
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        float ox = mDotX;
+        float oy = mDotY;
+
+        float x = event.getX();
+        float y = event.getY();
+
+        mDotX = x;
+
+        if (mDotX < mBorder) {
+            mDotX = mBorder;
+        }
+
+        if (mDotX > mWidth - mBorder) {
+            mDotX = mWidth - mBorder;
+        }
+        mHSVO[3] = (mDotX - mBorder) / (mWidth - mBorder * 2);
+        notifyColorListeners(mHSVO);
+        setupButton();
+        invalidate((int) (ox - mDotRadius), (int) (oy - mDotRadius), (int) (ox + mDotRadius),
+                (int) (oy + mDotRadius));
+        invalidate(
+                (int) (mDotX - mDotRadius), (int) (mDotY - mDotRadius), (int) (mDotX + mDotRadius),
+                (int) (mDotY + mDotRadius));
+
+        return true;
+    }
+
+    private void setupButton() {
+        float pos = mHSVO[3] * (mWidth - mBorder * 2);
+        mDotX = pos + mBorder;
+
+        int[] colors3 = new int[] {
+        mSliderColor, mSliderColor, 0x66000000, 0 };
+        RadialGradient g = new RadialGradient(mDotX, mDotY, mDotRadius, colors3, new float[] {
+        0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+        mDotPaint.setShader(g);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+        mHeight = h;
+        mDotY = mHeight / 2;
+        updatePaint();
+        setupButton();
+    }
+
+    private void updatePaint() {
+
+        int color2 = Color.HSVToColor(mHSVO);
+        int color1 = color2 & 0xFFFFFF;
+
+        Shader sg = new LinearGradient(
+                mBorder, mBorder, mWidth - mBorder, mBorder, color1, color2, Shader.TileMode.CLAMP);
+        mBarPaint1.setShader(sg);
+
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.drawColor(mBgcolor);
+        canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mCheckPaint);
+        canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mBarPaint1);
+        canvas.drawLine(mDotX, mDotY, mWidth - mBorder, mDotY, mLinePaint1);
+        canvas.drawLine(mBorder, mDotY, mDotX, mDotY, mLinePaint2);
+        if (mDotX != Float.NaN) {
+            canvas.drawCircle(mDotX, mDotY, mDotRadius, mDotPaint);
+        }
+    }
+
+    @Override
+    public void setColor(float[] hsv) {
+        System.arraycopy(hsv, 0, mHSVO, 0, mHSVO.length);
+
+        float oy = mDotY;
+
+        updatePaint();
+        setupButton();
+        invalidate();
+    }
+
+    ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+    public void notifyColorListeners(float[] hsvo) {
+        for (ColorListener l : mColorListeners) {
+            l.setColor(hsvo);
+        }
+    }
+
+    public void addColorListener(ColorListener l) {
+        mColorListeners.add(l);
+    }
+
+    public void removeColorListener(ColorListener l) {
+        mColorListeners.remove(l);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java
new file mode 100644
index 0000000..73a5c90
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorPickerDialog.java
@@ -0,0 +1,123 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ToggleButton;
+
+import com.android.gallery3d.R;
+
+public class ColorPickerDialog extends Dialog implements ColorListener {
+    ToggleButton mSelectedButton;
+    GradientDrawable mSelectRect;
+
+    float[] mHSVO = new float[4];
+
+    public ColorPickerDialog(Context context, final ColorListener cl) {
+        super(context);
+
+        setContentView(R.layout.filtershow_color_picker);
+        ColorValueView csv = (ColorValueView) findViewById(R.id.colorValueView);
+        ColorRectView cwv = (ColorRectView) findViewById(R.id.colorRectView);
+        ColorOpacityView cvv = (ColorOpacityView) findViewById(R.id.colorOpacityView);
+        float[] hsvo = new float[] {
+                123, .9f, 1, 1 };
+
+        mSelectRect = (GradientDrawable) getContext()
+                .getResources().getDrawable(R.drawable.filtershow_color_picker_roundrect);
+        Button selButton = (Button) findViewById(R.id.btnSelect);
+        selButton.setCompoundDrawablesWithIntrinsicBounds(null, null, mSelectRect, null);
+        Button sel = (Button) findViewById(R.id.btnSelect);
+
+        sel.setOnClickListener(new View.OnClickListener() {
+                @Override
+            public void onClick(View v) {
+                ColorPickerDialog.this.dismiss();
+                if (cl != null) {
+                    cl.setColor(mHSVO);
+                }
+            }
+        });
+
+        cwv.setColor(hsvo);
+        cvv.setColor(hsvo);
+        csv.setColor(hsvo);
+        csv.addColorListener(cwv);
+        cwv.addColorListener(csv);
+        csv.addColorListener(cvv);
+        cwv.addColorListener(cvv);
+        cvv.addColorListener(cwv);
+        cvv.addColorListener(csv);
+        cvv.addColorListener(this);
+        csv.addColorListener(this);
+        cwv.addColorListener(this);
+
+    }
+
+    void toggleClick(ToggleButton v, int[] buttons, boolean isChecked) {
+        int id = v.getId();
+        if (!isChecked) {
+            mSelectedButton = null;
+            return;
+        }
+        for (int i = 0; i < buttons.length; i++) {
+            if (id != buttons[i]) {
+                ToggleButton b = (ToggleButton) findViewById(buttons[i]);
+                b.setChecked(false);
+            }
+        }
+        mSelectedButton = v;
+
+        float[] hsv = (float[]) v.getTag();
+
+        ColorValueView csv = (ColorValueView) findViewById(R.id.colorValueView);
+        ColorRectView cwv = (ColorRectView) findViewById(R.id.colorRectView);
+        ColorOpacityView cvv = (ColorOpacityView) findViewById(R.id.colorOpacityView);
+        cwv.setColor(hsv);
+        cvv.setColor(hsv);
+        csv.setColor(hsv);
+    }
+
+    @Override
+    public void setColor(float[] hsvo) {
+        System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+        int color = Color.HSVToColor(hsvo);
+        mSelectRect.setColor(color);
+        setButtonColor(mSelectedButton, hsvo);
+    }
+
+    private void setButtonColor(ToggleButton button, float[] hsv) {
+        if (button == null) {
+            return;
+        }
+        int color = Color.HSVToColor(hsv);
+        button.setBackgroundColor(color);
+        float[] fg = new float[] {
+                (hsv[0] + 180) % 360,
+                hsv[1],
+                        (hsv[2] > .5f) ? .1f : .9f
+        };
+        button.setTextColor(Color.HSVToColor(fg));
+        button.setTag(hsv);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java
new file mode 100644
index 0000000..07d7c71
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorRectView.java
@@ -0,0 +1,225 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorRectView extends View implements ColorListener {
+    private float mDpToPix;
+    private float mRadius = 80;
+    private float mCtrY = 100;
+    private Paint mWheelPaint1;
+    private Paint mWheelPaint2;
+    private Paint mWheelPaint3;
+    private float mCtrX = 100;
+    private Paint mDotPaint;
+    private float mDotRadus;
+    private float mBorder;
+    private int mBgcolor = 0;
+    private float mDotX = Float.NaN;
+    private float mDotY;
+    private int mSliderColor = 0xFF33B5E5;
+    private float[] mHSVO = new float[4];
+    private int[] mColors = new int[] {
+            0xFFFF0000,// red
+            0xFFFFFF00,// yellow
+            0xFF00FF00,// green
+            0xFF00FFFF,// cyan
+            0xFF0000FF,// blue
+            0xFFFF00FF,// magenta
+            0xFFFF0000,// red
+    };
+    private int mWidth;
+    private int mHeight;
+    public final static float DOT_SIZE = 20;
+    public final static float BORDER_SIZE = 10;
+
+    public ColorRectView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+
+        DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+        mDpToPix = metrics.density;
+        mDotRadus = DOT_SIZE * mDpToPix;
+        mBorder = BORDER_SIZE * mDpToPix;
+
+        mWheelPaint1 = new Paint();
+        mWheelPaint2 = new Paint();
+        mWheelPaint3 = new Paint();
+        mDotPaint = new Paint();
+
+        mDotPaint.setStyle(Paint.Style.FILL);
+        mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+        mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+        mWheelPaint1.setStyle(Paint.Style.FILL);
+        mWheelPaint2.setStyle(Paint.Style.FILL);
+        mWheelPaint3.setStyle(Paint.Style.FILL);
+    }
+
+    public boolean onDown(MotionEvent e) {
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+
+        invalidate((int) (mDotX - mDotRadus), (int) (mDotY - mDotRadus), (int) (mDotX + mDotRadus),
+                (int) (mDotY + mDotRadus));
+        float x = event.getX();
+        float y = event.getY();
+
+        x = Math.max(Math.min(x, mWidth - mBorder), mBorder);
+        y = Math.max(Math.min(y, mHeight - mBorder), mBorder);
+        mDotX = x;
+        mDotY = y;
+        float sat = 1 - (mDotY - mBorder) / (mHeight - 2 * mBorder);
+        if (sat > 1) {
+            sat = 1;
+        }
+
+        double hue = Math.PI * 2 * (mDotX - mBorder) / (mHeight - 2 * mBorder);
+        mHSVO[0] = ((float) Math.toDegrees(hue) + 360) % 360;
+        mHSVO[1] = sat;
+        notifyColorListeners(mHSVO);
+        updateDotPaint();
+        invalidate((int) (mDotX - mDotRadus), (int) (mDotY - mDotRadus), (int) (mDotX + mDotRadus),
+                (int) (mDotY + mDotRadus));
+
+        return true;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+        mHeight = h;
+        mCtrY = h / 2f;
+        mCtrX = w / 2f;
+        mRadius = Math.min(mCtrY, mCtrX) - 2 * mBorder;
+        setUpColorPanel();
+    }
+
+    private void setUpColorPanel() {
+        float val = mHSVO[2];
+        int v = 0xFF000000 | 0x10101 * (int) (val * 0xFF);
+        int[] colors = new int[] {
+                0x0000000, v };
+        int[] colors2 = new int[] {
+                0x0000000, 0xFF000000 };
+        int[] wheelColor = new int[mColors.length];
+        float[] hsv = new float[3];
+        for (int i = 0; i < wheelColor.length; i++) {
+            Color.colorToHSV(mColors[i], hsv);
+            hsv[2] = mHSVO[2];
+            wheelColor[i] = Color.HSVToColor(hsv);
+        }
+        updateDot();
+        updateDotPaint();
+        SweepGradient sg = new SweepGradient(mCtrX, mCtrY, wheelColor, null);
+        LinearGradient lg = new LinearGradient(
+                mBorder, 0, mWidth - mBorder, 0, wheelColor, null, Shader.TileMode.CLAMP);
+
+        mWheelPaint1.setShader(lg);
+        LinearGradient rg = new LinearGradient(
+                0, mBorder, 0, mHeight - mBorder, colors, null, Shader.TileMode.CLAMP);
+        mWheelPaint2.setShader(rg);
+        LinearGradient rg2 = new LinearGradient(
+                0, mBorder, 0, mHeight - mBorder, colors2, null, Shader.TileMode.CLAMP);
+        mWheelPaint3.setShader(rg2);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.drawColor(mBgcolor);
+        RectF rect = new RectF();
+        rect.left = mBorder;
+        rect.right = mWidth - mBorder;
+        rect.top = mBorder;
+        rect.bottom = mHeight - mBorder;
+
+        canvas.drawRect(rect, mWheelPaint1);
+        canvas.drawRect(rect, mWheelPaint3);
+        canvas.drawRect(rect, mWheelPaint2);
+
+        if (mDotX != Float.NaN) {
+
+            canvas.drawCircle(mDotX, mDotY, mDotRadus, mDotPaint);
+        }
+    }
+
+    private void updateDot() {
+
+        double hue = mHSVO[0];
+        double sat = mHSVO[1];
+
+        mDotX = (float) (mBorder + (mHeight - 2 * mBorder) * Math.toRadians(hue) / (Math.PI * 2));
+        mDotY = (float) ((1 - sat) * (mHeight - 2 * mBorder) + mBorder);
+
+    }
+
+    private void updateDotPaint() {
+        int[] colors3 = new int[] {
+                mSliderColor, mSliderColor, 0x66000000, 0 };
+        RadialGradient g = new RadialGradient(mDotX, mDotY, mDotRadus, colors3, new float[] {
+                0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+        mDotPaint.setShader(g);
+
+    }
+
+    @Override
+    public void setColor(float[] hsvo) {
+        System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+
+        setUpColorPanel();
+        invalidate();
+
+        updateDot();
+        updateDotPaint();
+
+    }
+
+    ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+    public void notifyColorListeners(float[] hsv) {
+        for (ColorListener l : mColorListeners) {
+            l.setColor(hsv);
+        }
+    }
+
+    public void addColorListener(ColorListener l) {
+        mColorListeners.add(l);
+    }
+
+    public void removeColorListener(ColorListener l) {
+        mColorListeners.remove(l);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java b/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java
new file mode 100644
index 0000000..13cb44b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/ColorValueView.java
@@ -0,0 +1,180 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class ColorValueView extends View implements ColorListener {
+
+    private float mRadius;
+    private float mWidth;
+    private Paint mBarPaint1;
+    private Paint mLinePaint1;
+    private Paint mLinePaint2;
+    private float mHeight;
+    private int mBgcolor = 0;
+    private Paint mDotPaint;
+    private float dotRadus;
+    private float mBorder;
+
+    private float[] mHSVO = new float[4];
+    private int mSliderColor;
+    private float mDotX;
+    private float mDotY = mBorder;
+    private final static float DOT_SIZE = ColorRectView.DOT_SIZE;
+    private final static float BORDER_SIZE = ColorRectView.DOT_SIZE;
+
+    public ColorValueView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+        DisplayMetrics metrics = ctx.getResources().getDisplayMetrics();
+        float mDpToPix = metrics.density;
+        dotRadus = DOT_SIZE * mDpToPix;
+        mBorder = BORDER_SIZE * mDpToPix;
+
+        mBarPaint1 = new Paint();
+
+        mDotPaint = new Paint();
+
+        mDotPaint.setStyle(Paint.Style.FILL);
+        mDotPaint.setColor(ctx.getResources().getColor(R.color.slider_dot_color));
+
+        mBarPaint1.setStyle(Paint.Style.FILL);
+
+        mLinePaint1 = new Paint();
+        mLinePaint1.setColor(Color.GRAY);
+        mLinePaint2 = new Paint();
+        mSliderColor = ctx.getResources().getColor(R.color.slider_line_color);
+        mLinePaint2.setColor(mSliderColor);
+        mLinePaint2.setStrokeWidth(4);
+    }
+
+    public boolean onDown(MotionEvent e) {
+        return true;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        float ox = mDotX;
+        float oy = mDotY;
+
+        float x = event.getX();
+        float y = event.getY();
+
+        mDotY = y;
+
+        if (mDotY < mBorder) {
+            mDotY = mBorder;
+        }
+
+        if (mDotY > mHeight - mBorder) {
+            mDotY = mHeight - mBorder;
+        }
+        mHSVO[2] = (mDotY - mBorder) / (mHeight - mBorder * 2);
+        notifyColorListeners(mHSVO);
+        setupButton();
+        invalidate((int) (ox - dotRadus), (int) (oy - dotRadus), (int) (ox + dotRadus),
+                (int) (oy + dotRadus));
+        invalidate((int) (mDotX - dotRadus), (int) (mDotY - dotRadus), (int) (mDotX + dotRadus),
+                (int) (mDotY + dotRadus));
+
+        return true;
+    }
+
+    private void setupButton() {
+        float pos = mHSVO[2] * (mHeight - mBorder * 2);
+        mDotY = pos + mBorder;
+
+        int[] colors3 = new int[] {
+                mSliderColor, mSliderColor, 0x66000000, 0 };
+        RadialGradient g = new RadialGradient(mDotX, mDotY, dotRadus, colors3, new float[] {
+        0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+        mDotPaint.setShader(g);
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+        mHeight = h;
+        mDotX = mWidth / 2;
+        updatePaint();
+        setupButton();
+    }
+
+    private void updatePaint() {
+        float[] hsv = new float[] {
+                mHSVO[0], mHSVO[1], 0f };
+        int color1 = Color.HSVToColor(hsv);
+        hsv[2] = 1;
+        int color2 = Color.HSVToColor(hsv);
+
+        Shader sg = new LinearGradient(mBorder, mBorder, mBorder, mHeight - mBorder, color1, color2,
+                Shader.TileMode.CLAMP);
+        mBarPaint1.setShader(sg);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        canvas.drawColor(mBgcolor);
+        canvas.drawRect(mBorder, mBorder, mWidth - mBorder, mHeight - mBorder, mBarPaint1);
+        canvas.drawLine(mDotX, mDotY, mDotX, mHeight - mBorder, mLinePaint2);
+        canvas.drawLine(mDotX, mBorder, mDotX, mDotY, mLinePaint1);
+        if (mDotX != Float.NaN) {
+            canvas.drawCircle(mDotX, mDotY, dotRadus, mDotPaint);
+        }
+    }
+
+    @Override
+    public void setColor(float[] hsvo) {
+        System.arraycopy(hsvo, 0, mHSVO, 0, mHSVO.length);
+
+        float oy = mDotY;
+        updatePaint();
+        setupButton();
+        invalidate();
+
+    }
+
+    ArrayList<ColorListener> mColorListeners = new ArrayList<ColorListener>();
+
+    public void notifyColorListeners(float[] hsv) {
+        for (ColorListener l : mColorListeners) {
+            l.setColor(hsv);
+        }
+    }
+
+    public void addColorListener(ColorListener l) {
+        mColorListeners.add(l);
+    }
+
+    public void removeColorListener(ColorListener l) {
+        mColorListeners.remove(l);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java b/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java
new file mode 100644
index 0000000..147fb91
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/colorpicker/RGBListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.colorpicker;
+
+public interface RGBListener {
+    void setColor(int hsv);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ActionSlider.java b/src/com/android/gallery3d/filtershow/controller/ActionSlider.java
new file mode 100644
index 0000000..f80a1ca
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ActionSlider.java
@@ -0,0 +1,71 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class ActionSlider extends TitledSlider {
+    private static final String LOGTAG = "ActionSlider";
+    ImageButton mLeftButton;
+    ImageButton mRightButton;
+    public ActionSlider() {
+        mLayoutID = R.layout.filtershow_control_action_slider;
+    }
+
+    @Override
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        super.setUp(container, parameter, editor);
+        mLeftButton = (ImageButton) mTopView.findViewById(R.id.leftActionButton);
+        mLeftButton.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View v) {
+                ((ParameterActionAndInt) mParameter).fireLeftAction();
+            }
+        });
+
+        mRightButton = (ImageButton) mTopView.findViewById(R.id.rightActionButton);
+        mRightButton.setOnClickListener(new OnClickListener() {
+
+                @Override
+            public void onClick(View v) {
+                ((ParameterActionAndInt) mParameter).fireRightAction();
+            }
+        });
+        updateUI();
+    }
+
+    @Override
+    public void updateUI() {
+        super.updateUI();
+        if (mLeftButton != null) {
+            int iconId = ((ParameterActionAndInt) mParameter).getLeftIcon();
+            mLeftButton.setImageResource(iconId);
+        }
+        if (mRightButton != null) {
+            int iconId = ((ParameterActionAndInt) mParameter).getRightIcon();
+            mRightButton.setImageResource(iconId);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java
new file mode 100644
index 0000000..777bc43
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterInt.java
@@ -0,0 +1,105 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.util.Log;
+
+public class BasicParameterInt implements ParameterInteger {
+    protected String mParameterName;
+    protected Control mControl;
+    protected int mMaximum = 100;
+    protected int mMinimum = 0;
+    protected int mDefaultValue;
+    protected int mValue;
+    public final int ID;
+    protected FilterView mEditor;
+    private final String LOGTAG = "BasicParameterInt";
+
+    @Override
+    public void copyFrom(Parameter src) {
+        if (!(src instanceof BasicParameterInt)) {
+            throw new IllegalArgumentException(src.getClass().getName());
+        }
+        BasicParameterInt p = (BasicParameterInt) src;
+        mMaximum = p.mMaximum;
+        mMinimum = p.mMinimum;
+        mDefaultValue = p.mDefaultValue;
+        mValue = p.mValue;
+    }
+
+    public BasicParameterInt(int id, int value) {
+        ID = id;
+        mValue = value;
+    }
+    @Override
+    public String getParameterName() {
+        return mParameterName;
+    }
+
+    @Override
+    public String getParameterType() {
+        return sParameterType;
+    }
+
+    @Override
+    public String getValueString() {
+        return mParameterName + mValue;
+    }
+
+    @Override
+    public void setController(Control control) {
+        mControl = control;
+    }
+
+    @Override
+    public int getMaximum() {
+        return mMaximum;
+    }
+
+    @Override
+    public int getMinimum() {
+        return mMinimum;
+    }
+
+    @Override
+    public int getDefaultValue() {
+        return mDefaultValue;
+    }
+
+    @Override
+    public int getValue() {
+        return mValue;
+    }
+
+    @Override
+    public void setValue(int value) {
+        mValue = value;
+        if (mEditor != null) {
+            mEditor.commitLocalRepresentation();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getValueString();
+    }
+
+    @Override
+    public void setFilterView(FilterView editor) {
+        mEditor = editor;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
new file mode 100644
index 0000000..072edd7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
@@ -0,0 +1,113 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+
+public class BasicParameterStyle implements ParameterStyles {
+    protected String mParameterName;
+    protected int mSelectedStyle;
+    protected int mNumberOfStyles;
+    protected int mDefaultStyle = 0;
+    protected Control mControl;
+    protected FilterView mEditor;
+    public final int ID;
+    private final String LOGTAG = "BasicParameterStyle";
+
+    @Override
+    public void copyFrom(Parameter src) {
+        if (!(src instanceof BasicParameterStyle)) {
+            throw new IllegalArgumentException(src.getClass().getName());
+        }
+        BasicParameterStyle p = (BasicParameterStyle) src;
+        mNumberOfStyles = p.mNumberOfStyles;
+        mSelectedStyle = p.mSelectedStyle;
+        mDefaultStyle = p.mDefaultStyle;
+    }
+
+    public BasicParameterStyle(int id, int numberOfStyles) {
+        ID = id;
+        mNumberOfStyles = numberOfStyles;
+    }
+
+    @Override
+    public String getParameterName() {
+        return mParameterName;
+    }
+
+    @Override
+    public String getParameterType() {
+        return sParameterType;
+    }
+
+    @Override
+    public String getValueString() {
+        return mParameterName + mSelectedStyle;
+    }
+
+    @Override
+    public void setController(Control control) {
+        mControl = control;
+    }
+
+    @Override
+    public int getNumberOfStyles() {
+        return mNumberOfStyles;
+    }
+
+    @Override
+    public int getDefaultSelected() {
+        return mDefaultStyle;
+    }
+
+    @Override
+    public int getSelected() {
+        return mSelectedStyle;
+    }
+
+    @Override
+    public void setSelected(int selectedStyle) {
+        mSelectedStyle = selectedStyle;
+        if (mEditor != null) {
+            mEditor.commitLocalRepresentation();
+        }
+    }
+
+    @Override
+    public void getIcon(int index, RenderingRequestCaller caller) {
+        mEditor.computeIcon(index, caller);
+    }
+
+    @Override
+    public String getStyleTitle(int index, Context context) {
+        return "";
+    }
+
+    @Override
+    public String toString() {
+        return getValueString();
+    }
+
+    @Override
+    public void setFilterView(FilterView editor) {
+        mEditor = editor;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
new file mode 100644
index 0000000..df5b6ae
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
@@ -0,0 +1,88 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class BasicSlider implements Control {
+    private SeekBar mSeekBar;
+    private ParameterInteger mParameter;
+    Editor mEditor;
+
+    @Override
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
+        mEditor = editor;
+        Context context = container.getContext();
+        mParameter = (ParameterInteger) parameter;
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        LinearLayout lp = (LinearLayout) inflater.inflate(
+                R.layout.filtershow_seekbar, container, true);
+        mSeekBar = (SeekBar) lp.findViewById(R.id.primarySeekBar);
+
+        updateUI();
+        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (mParameter != null) {
+                    mParameter.setValue(progress + mParameter.getMinimum());
+                    mEditor.commitLocalRepresentation();
+
+                }
+            }
+        });
+    }
+
+    @Override
+    public View getTopView() {
+        return mSeekBar;
+    }
+
+    @Override
+    public void setPrameter(Parameter parameter) {
+        mParameter = (ParameterInteger) parameter;
+        if (mSeekBar != null) {
+            updateUI();
+        }
+    }
+
+    @Override
+    public void updateUI() {
+        mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum());
+        mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum());
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/Control.java b/src/com/android/gallery3d/filtershow/controller/Control.java
new file mode 100644
index 0000000..4342290
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/Control.java
@@ -0,0 +1,32 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public interface Control {
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor);
+
+    public View getTopView();
+
+    public void setPrameter(Parameter parameter);
+
+    public void updateUI();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/FilterView.java b/src/com/android/gallery3d/filtershow/controller/FilterView.java
new file mode 100644
index 0000000..2be7f36
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/FilterView.java
@@ -0,0 +1,25 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+
+public interface FilterView {
+    public void computeIcon(int index, RenderingRequestCaller caller);
+
+    public void commitLocalRepresentation();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/Parameter.java b/src/com/android/gallery3d/filtershow/controller/Parameter.java
new file mode 100644
index 0000000..8f4d5c0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/Parameter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public interface Parameter {
+    String getParameterName();
+
+    String getParameterType();
+
+    String getValueString();
+
+    public void setController(Control c);
+
+    public void setFilterView(FilterView editor);
+
+    public void copyFrom(Parameter src);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java b/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java
new file mode 100644
index 0000000..8a05c3a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterActionAndInt.java
@@ -0,0 +1,29 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+public interface ParameterActionAndInt extends ParameterInteger {
+    static String sParameterType = "ParameterActionAndInt";
+
+    public void fireLeftAction();
+
+    public int getLeftIcon();
+
+    public void fireRightAction();
+
+    public int getRightIcon();
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java b/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java
new file mode 100644
index 0000000..0bfd201
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterInteger.java
@@ -0,0 +1,31 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+public interface ParameterInteger extends Parameter {
+    static String sParameterType = "ParameterInteger";
+
+    int getMaximum();
+
+    int getMinimum();
+
+    int getDefaultValue();
+
+    int getValue();
+
+    void setValue(int value);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterSet.java b/src/com/android/gallery3d/filtershow/controller/ParameterSet.java
new file mode 100644
index 0000000..6b50a4d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterSet.java
@@ -0,0 +1,23 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+public interface ParameterSet {
+    int getNumberOfParameters();
+
+    Parameter getFilterParameter(int index);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
new file mode 100644
index 0000000..c267d3d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
@@ -0,0 +1,37 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.content.Context;
+
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+
+public interface ParameterStyles extends Parameter {
+    public static String sParameterType = "ParameterStyles";
+
+    int getNumberOfStyles();
+
+    int getDefaultSelected();
+
+    int getSelected();
+
+    void setSelected(int value);
+
+    void getIcon(int index, RenderingRequestCaller caller);
+
+    String getStyleTitle(int index, Context context);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
new file mode 100644
index 0000000..b3d0de7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
@@ -0,0 +1,88 @@
+package com.android.gallery3d.filtershow.controller;
+
+import android.app.ActionBar.LayoutParams;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.RenderingRequest;
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+import java.util.Vector;
+
+public class StyleChooser implements Control {
+    private final String LOGTAG = "StyleChooser";
+    protected ParameterStyles mParameter;
+    protected LinearLayout mLinearLayout;
+    protected Editor mEditor;
+    private View mTopView;
+    private Vector<ImageButton> mIconButton = new Vector<ImageButton>();
+    protected int mLayoutID = R.layout.filtershow_control_style_chooser;
+
+    @Override
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
+        mEditor = editor;
+        Context context = container.getContext();
+        mParameter = (ParameterStyles) parameter;
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mTopView = inflater.inflate(mLayoutID, container, true);
+        mLinearLayout = (LinearLayout) mTopView.findViewById(R.id.listStyles);
+        mTopView.setVisibility(View.VISIBLE);
+        int n = mParameter.getNumberOfStyles();
+        mIconButton.clear();
+        LayoutParams lp = new LayoutParams(120, 120);
+        for (int i = 0; i < n; i++) {
+            final ImageButton button = new ImageButton(context);
+            button.setScaleType(ScaleType.CENTER_CROP);
+            button.setLayoutParams(lp);
+            button.setBackgroundResource(android.R.color.transparent);
+            mIconButton.add(button);
+            final int buttonNo = i;
+            button.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View arg0) {
+                    mParameter.setSelected(buttonNo);
+                }
+            });
+            mLinearLayout.addView(button);
+            mParameter.getIcon(i, new RenderingRequestCaller() {
+                @Override
+                public void available(RenderingRequest request) {
+                    Bitmap bmap = request.getBitmap();
+                    if (bmap == null) {
+                        return;
+                    }
+                    button.setImageBitmap(bmap);
+                }
+            });
+        }
+    }
+
+    @Override
+    public View getTopView() {
+        return mTopView;
+    }
+
+    @Override
+    public void setPrameter(Parameter parameter) {
+        mParameter = (ParameterStyles) parameter;
+        updateUI();
+    }
+
+    @Override
+    public void updateUI() {
+        if (mParameter == null) {
+            return;
+        }
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/TitledSlider.java b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
new file mode 100644
index 0000000..f29442b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
@@ -0,0 +1,106 @@
+/*
+ * 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.gallery3d.filtershow.controller;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+public class TitledSlider implements Control {
+    private final String LOGTAG = "ParametricEditor";
+    private SeekBar mSeekBar;
+    private TextView mControlName;
+    private TextView mControlValue;
+    protected ParameterInteger mParameter;
+    Editor mEditor;
+    View mTopView;
+    protected int mLayoutID = R.layout.filtershow_control_title_slider;
+
+    @Override
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
+        mEditor = editor;
+        Context context = container.getContext();
+        mParameter = (ParameterInteger) parameter;
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mTopView = inflater.inflate(mLayoutID, container, true);
+        mTopView.setVisibility(View.VISIBLE);
+        mSeekBar = (SeekBar) mTopView.findViewById(R.id.controlValueSeekBar);
+        mControlName = (TextView) mTopView.findViewById(R.id.controlName);
+        mControlValue = (TextView) mTopView.findViewById(R.id.controlValue);
+        updateUI();
+        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+            }
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                if (mParameter != null) {
+                    mParameter.setValue(progress + mParameter.getMinimum());
+                    if (mControlName != null) {
+                        mControlName.setText(mParameter.getParameterName());
+                    }
+                    if (mControlValue != null) {
+                        mControlValue.setText(Integer.toString(mParameter.getValue()));
+                    }
+                    mEditor.commitLocalRepresentation();
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setPrameter(Parameter parameter) {
+        mParameter = (ParameterInteger) parameter;
+        if (mSeekBar != null)
+            updateUI();
+    }
+
+    @Override
+    public void updateUI() {
+        if (mControlName != null && mParameter.getParameterName() != null) {
+            mControlName.setText(mParameter.getParameterName().toUpperCase());
+        }
+        if (mControlValue != null) {
+            mControlValue.setText(
+                    Integer.toString(mParameter.getValue()));
+        }
+        mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum());
+        mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum());
+        mEditor.commitLocalRepresentation();
+    }
+
+    @Override
+    public View getTopView() {
+        return mTopView;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/BoundedRect.java b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
new file mode 100644
index 0000000..74ce7cd
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/BoundedRect.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.crop;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
+import java.util.Arrays;
+
+/**
+ * Maintains invariant that inner rectangle is constrained to be within the
+ * outer, rotated rectangle.
+ */
+public class BoundedRect {
+    private float rot;
+    private RectF outer;
+    private RectF inner;
+    private float[] innerRotated;
+
+    public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
+        rot = rotation;
+        outer = new RectF(outerRect);
+        inner = new RectF(innerRect);
+        innerRotated = CropMath.getCornersFromRect(inner);
+        rotateInner();
+        if (!isConstrained())
+            reconstrain();
+    }
+
+    public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
+        rot = rotation;
+        outer = new RectF(outerRect);
+        inner = new RectF(innerRect);
+        innerRotated = CropMath.getCornersFromRect(inner);
+        rotateInner();
+        if (!isConstrained())
+            reconstrain();
+    }
+
+    public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
+        rot = rotation;
+        outer.set(outerRect);
+        inner.set(innerRect);
+        innerRotated = CropMath.getCornersFromRect(inner);
+        rotateInner();
+        if (!isConstrained())
+            reconstrain();
+    }
+
+    /**
+     * Sets inner, and re-constrains it to fit within the rotated bounding rect.
+     */
+    public void setInner(RectF newInner) {
+        if (inner.equals(newInner))
+            return;
+        inner = newInner;
+        innerRotated = CropMath.getCornersFromRect(inner);
+        rotateInner();
+        if (!isConstrained())
+            reconstrain();
+    }
+
+    /**
+     * Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
+     */
+    public void setRotation(float rotation) {
+        if (rotation == rot)
+            return;
+        rot = rotation;
+        innerRotated = CropMath.getCornersFromRect(inner);
+        rotateInner();
+        if (!isConstrained())
+            reconstrain();
+    }
+
+    public void setToInner(RectF r) {
+        r.set(inner);
+    }
+
+    public void setToOuter(RectF r) {
+        r.set(outer);
+    }
+
+    public RectF getInner() {
+        return new RectF(inner);
+    }
+
+    public RectF getOuter() {
+        return new RectF(outer);
+    }
+
+    /**
+     * Tries to move the inner rectangle by (dx, dy).  If this would cause it to leave
+     * the bounding rectangle, snaps the inner rectangle to the edge of the bounding
+     * rectangle.
+     */
+    public void moveInner(float dx, float dy) {
+        Matrix m0 = getInverseRotMatrix();
+
+        RectF translatedInner = new RectF(inner);
+        translatedInner.offset(dx, dy);
+
+        float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
+        float[] outerCorners = CropMath.getCornersFromRect(outer);
+
+        m0.mapPoints(translatedInnerCorners);
+        float[] correction = {
+                0, 0
+        };
+
+        // find correction vectors for corners that have moved out of bounds
+        for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+            float correctedInnerX = translatedInnerCorners[i] + correction[0];
+            float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+            if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
+                float[] badCorner = {
+                        correctedInnerX, correctedInnerY
+                };
+                float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
+                float[] correctionVec =
+                        GeometryMath.shortestVectorFromPointToLine(badCorner, nearestSide);
+                correction[0] += correctionVec[0];
+                correction[1] += correctionVec[1];
+            }
+        }
+
+        for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+            float correctedInnerX = translatedInnerCorners[i] + correction[0];
+            float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+            if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
+                float[] correctionVec = {
+                        correctedInnerX, correctedInnerY
+                };
+                CropMath.getEdgePoints(outer, correctionVec);
+                correctionVec[0] -= correctedInnerX;
+                correctionVec[1] -= correctedInnerY;
+                correction[0] += correctionVec[0];
+                correction[1] += correctionVec[1];
+            }
+        }
+
+        // Set correction
+        for (int i = 0; i < translatedInnerCorners.length; i += 2) {
+            float correctedInnerX = translatedInnerCorners[i] + correction[0];
+            float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
+            // update translated corners with correction vectors
+            translatedInnerCorners[i] = correctedInnerX;
+            translatedInnerCorners[i + 1] = correctedInnerY;
+        }
+
+        innerRotated = translatedInnerCorners;
+        // reconstrain to update inner
+        reconstrain();
+    }
+
+    /**
+     * Attempts to resize the inner rectangle.  If this would cause it to leave
+     * the bounding rect, clips the inner rectangle to fit.
+     */
+    public void resizeInner(RectF newInner) {
+        Matrix m = getRotMatrix();
+        Matrix m0 = getInverseRotMatrix();
+
+        float[] outerCorners = CropMath.getCornersFromRect(outer);
+        m.mapPoints(outerCorners);
+        float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
+        float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
+        RectF ret = new RectF(newInner);
+
+        for (int i = 0; i < newInnerCorners.length; i += 2) {
+            float[] c = {
+                    newInnerCorners[i], newInnerCorners[i + 1]
+            };
+            float[] c0 = Arrays.copyOf(c, 2);
+            m0.mapPoints(c0);
+            if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
+                float[] outerSide = CropMath.closestSide(c, outerCorners);
+                float[] pathOfCorner = {
+                        newInnerCorners[i], newInnerCorners[i + 1],
+                        oldInnerCorners[i], oldInnerCorners[i + 1]
+                };
+                float[] p = GeometryMath.lineIntersect(pathOfCorner, outerSide);
+                if (p == null) {
+                    // lines are parallel or not well defined, so don't resize
+                    p = new float[2];
+                    p[0] = oldInnerCorners[i];
+                    p[1] = oldInnerCorners[i + 1];
+                }
+                // relies on corners being in same order as method
+                // getCornersFromRect
+                switch (i) {
+                    case 0:
+                    case 1:
+                        ret.left = (p[0] > ret.left) ? p[0] : ret.left;
+                        ret.top = (p[1] > ret.top) ? p[1] : ret.top;
+                        break;
+                    case 2:
+                    case 3:
+                        ret.right = (p[0] < ret.right) ? p[0] : ret.right;
+                        ret.top = (p[1] > ret.top) ? p[1] : ret.top;
+                        break;
+                    case 4:
+                    case 5:
+                        ret.right = (p[0] < ret.right) ? p[0] : ret.right;
+                        ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
+                        break;
+                    case 6:
+                    case 7:
+                        ret.left = (p[0] > ret.left) ? p[0] : ret.left;
+                        ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        float[] retCorners = CropMath.getCornersFromRect(ret);
+        m0.mapPoints(retCorners);
+        innerRotated = retCorners;
+        // reconstrain to update inner
+        reconstrain();
+    }
+
+    /**
+     * Attempts to resize the inner rectangle.  If this would cause it to leave
+     * the bounding rect, clips the inner rectangle to fit while maintaining
+     * aspect ratio.
+     */
+    public void fixedAspectResizeInner(RectF newInner) {
+        Matrix m = getRotMatrix();
+        Matrix m0 = getInverseRotMatrix();
+
+        float aspectW = inner.width();
+        float aspectH = inner.height();
+        float aspRatio = aspectW / aspectH;
+        float[] corners = CropMath.getCornersFromRect(outer);
+
+        m.mapPoints(corners);
+        float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
+        float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
+
+        // find fixed corner
+        int fixed = -1;
+        if (inner.top == newInner.top) {
+            if (inner.left == newInner.left)
+                fixed = 0; // top left
+            else if (inner.right == newInner.right)
+                fixed = 2; // top right
+        } else if (inner.bottom == newInner.bottom) {
+            if (inner.right == newInner.right)
+                fixed = 4; // bottom right
+            else if (inner.left == newInner.left)
+                fixed = 6; // bottom left
+        }
+        // no fixed corner, return without update
+        if (fixed == -1)
+            return;
+        float widthSoFar = newInner.width();
+        int moved = -1;
+        for (int i = 0; i < newInnerCorners.length; i += 2) {
+            float[] c = {
+                    newInnerCorners[i], newInnerCorners[i + 1]
+            };
+            float[] c0 = Arrays.copyOf(c, 2);
+            m0.mapPoints(c0);
+            if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
+                moved = i;
+                if (moved == fixed)
+                    continue;
+                float[] l2 = CropMath.closestSide(c, corners);
+                float[] l1 = {
+                        newInnerCorners[i], newInnerCorners[i + 1],
+                        oldInnerCorners[i], oldInnerCorners[i + 1]
+                };
+                float[] p = GeometryMath.lineIntersect(l1, l2);
+                if (p == null) {
+                    // lines are parallel or not well defined, so set to old
+                    // corner
+                    p = new float[2];
+                    p[0] = oldInnerCorners[i];
+                    p[1] = oldInnerCorners[i + 1];
+                }
+                // relies on corners being in same order as method
+                // getCornersFromRect
+                float fixed_x = oldInnerCorners[fixed];
+                float fixed_y = oldInnerCorners[fixed + 1];
+                float newWidth = Math.abs(fixed_x - p[0]);
+                float newHeight = Math.abs(fixed_y - p[1]);
+                newWidth = Math.max(newWidth, aspRatio * newHeight);
+                if (newWidth < widthSoFar)
+                    widthSoFar = newWidth;
+            }
+        }
+
+        float heightSoFar = widthSoFar / aspRatio;
+        RectF ret = new RectF(inner);
+        if (fixed == 0) {
+            ret.right = ret.left + widthSoFar;
+            ret.bottom = ret.top + heightSoFar;
+        } else if (fixed == 2) {
+            ret.left = ret.right - widthSoFar;
+            ret.bottom = ret.top + heightSoFar;
+        } else if (fixed == 4) {
+            ret.left = ret.right - widthSoFar;
+            ret.top = ret.bottom - heightSoFar;
+        } else if (fixed == 6) {
+            ret.right = ret.left + widthSoFar;
+            ret.top = ret.bottom - heightSoFar;
+        }
+        float[] retCorners = CropMath.getCornersFromRect(ret);
+        m0.mapPoints(retCorners);
+        innerRotated = retCorners;
+        // reconstrain to update inner
+        reconstrain();
+    }
+
+    // internal methods
+
+    private boolean isConstrained() {
+        for (int i = 0; i < 8; i += 2) {
+            if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
+                return false;
+        }
+        return true;
+    }
+
+    private void reconstrain() {
+        // innerRotated has been changed to have incorrect values
+        CropMath.getEdgePoints(outer, innerRotated);
+        Matrix m = getRotMatrix();
+        float[] unrotated = Arrays.copyOf(innerRotated, 8);
+        m.mapPoints(unrotated);
+        inner = CropMath.trapToRect(unrotated);
+    }
+
+    private void rotateInner() {
+        Matrix m = getInverseRotMatrix();
+        m.mapPoints(innerRotated);
+    }
+
+    private Matrix getRotMatrix() {
+        Matrix m = new Matrix();
+        m.setRotate(rot, outer.centerX(), outer.centerY());
+        return m;
+    }
+
+    private Matrix getInverseRotMatrix() {
+        Matrix m = new Matrix();
+        m.setRotate(-rot, outer.centerX(), outer.centerY());
+        return m;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
new file mode 100644
index 0000000..d349d5d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java
@@ -0,0 +1,695 @@
+/*
+ * 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.gallery3d.filtershow.crop;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Activity for cropping an image.
+ */
+public class CropActivity extends Activity {
+    private static final String LOGTAG = "CropActivity";
+    public static final String CROP_ACTION = "com.android.camera.action.CROP";
+    private CropExtras mCropExtras = null;
+    private LoadBitmapTask mLoadBitmapTask = null;
+
+    private int mOutputX = 0;
+    private int mOutputY = 0;
+    private Bitmap mOriginalBitmap = null;
+    private RectF mOriginalBounds = null;
+    private int mOriginalRotation = 0;
+    private Uri mSourceUri = null;
+    private CropView mCropView = null;
+    private View mSaveButton = null;
+    private boolean finalIOGuard = false;
+
+    private static final int SELECT_PICTURE = 1; // request code for picker
+
+    private static final int DEFAULT_COMPRESS_QUALITY = 90;
+    /**
+     * The maximum bitmap size we allow to be returned through the intent.
+     * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
+     * have some overhead to hit so that we go way below the limit here to make
+     * sure the intent stays below 1MB.We should consider just returning a byte
+     * array instead of a Bitmap instance to avoid overhead.
+     */
+    public static final int MAX_BMAP_IN_INTENT = 750000;
+
+    // Flags
+    private static final int DO_SET_WALLPAPER = 1;
+    private static final int DO_RETURN_DATA = 1 << 1;
+    private static final int DO_EXTRA_OUTPUT = 1 << 2;
+
+    private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+        setResult(RESULT_CANCELED, new Intent());
+        mCropExtras = getExtrasFromIntent(intent);
+        if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+        }
+
+        setContentView(R.layout.crop_activity);
+        mCropView = (CropView) findViewById(R.id.cropView);
+
+        ActionBar actionBar = getActionBar();
+        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
+        actionBar.setCustomView(R.layout.filtershow_actionbar);
+
+        View mSaveButton = actionBar.getCustomView();
+        mSaveButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                startFinishOutput();
+            }
+        });
+
+        if (intent.getData() != null) {
+            mSourceUri = intent.getData();
+            startLoadBitmap(mSourceUri);
+        } else {
+            pickImage();
+        }
+    }
+
+    private void enableSave(boolean enable) {
+        if (mSaveButton != null) {
+            mSaveButton.setEnabled(enable);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mLoadBitmapTask != null) {
+            mLoadBitmapTask.cancel(false);
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public void onConfigurationChanged (Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mCropView.configChanged();
+    }
+
+    /**
+     * Opens a selector in Gallery to chose an image for use when none was given
+     * in the CROP intent.
+     */
+    private void pickImage() {
+        Intent intent = new Intent();
+        intent.setType("image/*");
+        intent.setAction(Intent.ACTION_GET_CONTENT);
+        startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
+                SELECT_PICTURE);
+    }
+
+    /**
+     * Callback for pickImage().
+     */
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
+            mSourceUri = data.getData();
+            startLoadBitmap(mSourceUri);
+        }
+    }
+
+    /**
+     * Gets screen size metric.
+     */
+    private int getScreenImageSize() {
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
+        return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
+    }
+
+    /**
+     * Method that loads a bitmap in an async task.
+     */
+    private void startLoadBitmap(Uri uri) {
+        if (uri != null) {
+            enableSave(false);
+            final View loading = findViewById(R.id.loading);
+            loading.setVisibility(View.VISIBLE);
+            mLoadBitmapTask = new LoadBitmapTask();
+            mLoadBitmapTask.execute(uri);
+        } else {
+            cannotLoadImage();
+            done();
+        }
+    }
+
+    /**
+     * Method called on UI thread with loaded bitmap.
+     */
+    private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
+        final View loading = findViewById(R.id.loading);
+        loading.setVisibility(View.GONE);
+        mOriginalBitmap = bitmap;
+        mOriginalBounds = bounds;
+        mOriginalRotation = orientation;
+        if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
+            RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
+            mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
+            if (mCropExtras != null) {
+                int aspectX = mCropExtras.getAspectX();
+                int aspectY = mCropExtras.getAspectY();
+                mOutputX = mCropExtras.getOutputX();
+                mOutputY = mCropExtras.getOutputY();
+                if (mOutputX > 0 && mOutputY > 0) {
+                    mCropView.applyAspect(mOutputX, mOutputY);
+
+                }
+                float spotX = mCropExtras.getSpotlightX();
+                float spotY = mCropExtras.getSpotlightY();
+                if (spotX > 0 && spotY > 0) {
+                    mCropView.setWallpaperSpotlight(spotX, spotY);
+                }
+                if (aspectX > 0 && aspectY > 0) {
+                    mCropView.applyAspect(aspectX, aspectY);
+                }
+            }
+            enableSave(true);
+        } else {
+            Log.w(LOGTAG, "could not load image for cropping");
+            cannotLoadImage();
+            setResult(RESULT_CANCELED, new Intent());
+            done();
+        }
+    }
+
+    /**
+     * Display toast for image loading failure.
+     */
+    private void cannotLoadImage() {
+        CharSequence text = getString(R.string.cannot_load_image);
+        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
+        toast.show();
+    }
+
+    /**
+     * AsyncTask for loading a bitmap into memory.
+     *
+     * @see #startLoadBitmap(Uri)
+     * @see #doneLoadBitmap(Bitmap)
+     */
+    private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
+        int mBitmapSize;
+        Context mContext;
+        Rect mOriginalBounds;
+        int mOrientation;
+
+        public LoadBitmapTask() {
+            mBitmapSize = getScreenImageSize();
+            mContext = getApplicationContext();
+            mOriginalBounds = new Rect();
+            mOrientation = 0;
+        }
+
+        @Override
+        protected Bitmap doInBackground(Uri... params) {
+            Uri uri = params[0];
+            Bitmap bmap = CropLoader.getConstrainedBitmap(uri, mContext, mBitmapSize,
+                    mOriginalBounds);
+            mOrientation = CropLoader.getMetadataRotation(uri, mContext);
+            return bmap;
+        }
+
+        @Override
+        protected void onPostExecute(Bitmap result) {
+            doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
+        }
+    }
+
+    private void startFinishOutput() {
+        if (finalIOGuard) {
+            return;
+        } else {
+            finalIOGuard = true;
+        }
+        enableSave(false);
+        Uri destinationUri = null;
+        int flags = 0;
+        if (mOriginalBitmap != null && mCropExtras != null) {
+            if (mCropExtras.getExtraOutput() != null) {
+                destinationUri = mCropExtras.getExtraOutput();
+                if (destinationUri != null) {
+                    flags |= DO_EXTRA_OUTPUT;
+                }
+            }
+            if (mCropExtras.getSetAsWallpaper()) {
+                flags |= DO_SET_WALLPAPER;
+            }
+            if (mCropExtras.getReturnData()) {
+                flags |= DO_RETURN_DATA;
+            }
+        }
+        if (flags == 0) {
+            destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri);
+            if (destinationUri != null) {
+                flags |= DO_EXTRA_OUTPUT;
+            }
+        }
+        if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
+            RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
+            RectF crop = getBitmapCrop(photo);
+            startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
+                    photo, mOriginalBounds,
+                    (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
+            return;
+        }
+        setResult(RESULT_CANCELED, new Intent());
+        done();
+        return;
+    }
+
+    private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
+            RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
+            int rotation) {
+        if (cropBounds == null || photoBounds == null || currentBitmap == null
+                || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
+                || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
+                || photoBounds.height() == 0) {
+            return; // fail fast
+        }
+        if ((flags & FLAG_CHECK) == 0) {
+            return; // no output options
+        }
+        if ((flags & DO_SET_WALLPAPER) != 0) {
+            Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
+        }
+
+        final View loading = findViewById(R.id.loading);
+        loading.setVisibility(View.VISIBLE);
+        BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
+                photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
+        ioTask.execute(currentBitmap);
+    }
+
+    private void doneBitmapIO(boolean success, Intent intent) {
+        final View loading = findViewById(R.id.loading);
+        loading.setVisibility(View.GONE);
+        if (success) {
+            setResult(RESULT_OK, intent);
+        } else {
+            setResult(RESULT_CANCELED, intent);
+        }
+        done();
+    }
+
+    private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
+
+        private final WallpaperManager mWPManager;
+        InputStream mInStream = null;
+        OutputStream mOutStream = null;
+        String mOutputFormat = null;
+        Uri mOutUri = null;
+        Uri mInUri = null;
+        int mFlags = 0;
+        RectF mCrop = null;
+        RectF mPhoto = null;
+        RectF mOrig = null;
+        Intent mResultIntent = null;
+        int mRotation = 0;
+
+        // Helper to setup input stream
+        private void regenerateInputStream() {
+            if (mInUri == null) {
+                Log.w(LOGTAG, "cannot read original file, no input URI given");
+            } else {
+                Utils.closeSilently(mInStream);
+                try {
+                    mInStream = getContentResolver().openInputStream(mInUri);
+                } catch (FileNotFoundException e) {
+                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
+                }
+            }
+        }
+
+        public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
+                RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
+                int outputX, int outputY) {
+            mOutputFormat = outputFormat;
+            mOutStream = null;
+            mOutUri = destUri;
+            mInUri = sourceUri;
+            mFlags = flags;
+            mCrop = cropBounds;
+            mPhoto = photoBounds;
+            mOrig = originalBitmapBounds;
+            mWPManager = WallpaperManager.getInstance(getApplicationContext());
+            mResultIntent = new Intent();
+            mRotation = (rotation < 0) ? -rotation : rotation;
+            mRotation %= 360;
+            mRotation = 90 * (int) (mRotation / 90);  // now mRotation is a multiple of 90
+            mOutputX = outputX;
+            mOutputY = outputY;
+
+            if ((flags & DO_EXTRA_OUTPUT) != 0) {
+                if (mOutUri == null) {
+                    Log.w(LOGTAG, "cannot write file, no output URI given");
+                } else {
+                    try {
+                        mOutStream = getContentResolver().openOutputStream(mOutUri);
+                    } catch (FileNotFoundException e) {
+                        Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
+                    }
+                }
+            }
+
+            if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
+                regenerateInputStream();
+            }
+        }
+
+        @Override
+        protected Boolean doInBackground(Bitmap... params) {
+            boolean failure = false;
+            Bitmap img = params[0];
+
+            // Set extra for crop bounds
+            if (mCrop != null && mPhoto != null && mOrig != null) {
+                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+                Matrix m = new Matrix();
+                m.setRotate(mRotation);
+                m.mapRect(trueCrop);
+                if (trueCrop != null) {
+                    Rect rounded = new Rect();
+                    trueCrop.roundOut(rounded);
+                    mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
+                }
+            }
+
+            // Find the small cropped bitmap that is returned in the intent
+            if ((mFlags & DO_RETURN_DATA) != 0) {
+                assert (img != null);
+                Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
+                if (ret != null) {
+                    ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
+                }
+                if (ret == null) {
+                    Log.w(LOGTAG, "could not downsample bitmap to return in data");
+                    failure = true;
+                } else {
+                    if (mRotation > 0) {
+                        Matrix m = new Matrix();
+                        m.setRotate(mRotation);
+                        Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
+                                ret.getHeight(), m, true);
+                        if (tmp != null) {
+                            ret = tmp;
+                        }
+                    }
+                    mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
+                }
+            }
+
+            // Do the large cropped bitmap and/or set the wallpaper
+            if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
+                // Find crop bounds (scaled to original image size)
+                RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
+                if (trueCrop == null) {
+                    Log.w(LOGTAG, "cannot find crop for full size image");
+                    failure = true;
+                    return false;
+                }
+                Rect roundedTrueCrop = new Rect();
+                trueCrop.roundOut(roundedTrueCrop);
+
+                if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
+                    Log.w(LOGTAG, "crop has bad values for full size image");
+                    failure = true;
+                    return false;
+                }
+
+                // Attempt to open a region decoder
+                BitmapRegionDecoder decoder = null;
+                try {
+                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
+                } catch (IOException e) {
+                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
+                }
+
+                Bitmap crop = null;
+                if (decoder != null) {
+                    // Do region decoding to get crop bitmap
+                    BitmapFactory.Options options = new BitmapFactory.Options();
+                    options.inMutable = true;
+                    crop = decoder.decodeRegion(roundedTrueCrop, options);
+                    decoder.recycle();
+                }
+
+                if (crop == null) {
+                    // BitmapRegionDecoder has failed, try to crop in-memory
+                    regenerateInputStream();
+                    Bitmap fullSize = null;
+                    if (mInStream != null) {
+                        fullSize = BitmapFactory.decodeStream(mInStream);
+                    }
+                    if (fullSize != null) {
+                        crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
+                                roundedTrueCrop.top, roundedTrueCrop.width(),
+                                roundedTrueCrop.height());
+                    }
+                }
+
+                if (crop == null) {
+                    Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
+                    failure = true;
+                    return false;
+                }
+                if (mOutputX > 0 && mOutputY > 0) {
+                    Matrix m = new Matrix();
+                    RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
+                    if (mRotation > 0) {
+                        m.setRotate(mRotation);
+                        m.mapRect(cropRect);
+                    }
+                    RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
+                    m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
+                    m.preRotate(mRotation);
+                    Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
+                            (int) returnRect.height(), Bitmap.Config.ARGB_8888);
+                    if (tmp != null) {
+                        Canvas c = new Canvas(tmp);
+                        c.drawBitmap(crop, m, new Paint());
+                        crop = tmp;
+                    }
+                } else if (mRotation > 0) {
+                    Matrix m = new Matrix();
+                    m.setRotate(mRotation);
+                    Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
+                            crop.getHeight(), m, true);
+                    if (tmp != null) {
+                        crop = tmp;
+                    }
+                }
+                // Get output compression format
+                CompressFormat cf =
+                        convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
+
+                // If we only need to output to a URI, compress straight to file
+                if (mFlags == DO_EXTRA_OUTPUT) {
+                    if (mOutStream == null
+                            || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
+                        Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
+                        failure = true;
+                    } else {
+                        mResultIntent.setData(mOutUri);
+                    }
+                } else {
+                    // Compress to byte array
+                    ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
+                    if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
+
+                        // If we need to output to a Uri, write compressed
+                        // bitmap out
+                        if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
+                            if (mOutStream == null) {
+                                Log.w(LOGTAG,
+                                        "failed to compress bitmap to file: " + mOutUri.toString());
+                                failure = true;
+                            } else {
+                                try {
+                                    mOutStream.write(tmpOut.toByteArray());
+                                    mResultIntent.setData(mOutUri);
+                                } catch (IOException e) {
+                                    Log.w(LOGTAG,
+                                            "failed to compress bitmap to file: "
+                                                    + mOutUri.toString(), e);
+                                    failure = true;
+                                }
+                            }
+                        }
+
+                        // If we need to set to the wallpaper, set it
+                        if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
+                            if (mWPManager == null) {
+                                Log.w(LOGTAG, "no wallpaper manager");
+                                failure = true;
+                            } else {
+                                try {
+                                    mWPManager.setStream(new ByteArrayInputStream(tmpOut
+                                            .toByteArray()));
+                                } catch (IOException e) {
+                                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
+                                    failure = true;
+                                }
+                            }
+                        }
+                    } else {
+                        Log.w(LOGTAG, "cannot compress bitmap");
+                        failure = true;
+                    }
+                }
+            }
+            return !failure; // True if any of the operations failed
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            Utils.closeSilently(mOutStream);
+            Utils.closeSilently(mInStream);
+            doneBitmapIO(result.booleanValue(), mResultIntent);
+        }
+
+    }
+
+    private void done() {
+        finish();
+    }
+
+    protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
+        RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
+        RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
+        if (crop == null) {
+            return null;
+        }
+        Rect intCrop = new Rect();
+        crop.roundOut(intCrop);
+        return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
+                intCrop.height());
+    }
+
+    protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
+        if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
+            throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
+        }
+        int shifts = 0;
+        int size = CropMath.getBitmapSize(image);
+        while (size > max_size) {
+            shifts++;
+            size /= 4;
+        }
+        Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
+                image.getHeight() >> shifts, true);
+        if (ret == null) {
+            return null;
+        }
+        // Handle edge case for rounding.
+        if (CropMath.getBitmapSize(ret) > max_size) {
+            return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
+        }
+        return ret;
+    }
+
+    /**
+     * Gets the crop extras from the intent, or null if none exist.
+     */
+    protected static CropExtras getExtrasFromIntent(Intent intent) {
+        Bundle extras = intent.getExtras();
+        if (extras != null) {
+            return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
+                    extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
+                    extras.getBoolean(CropExtras.KEY_SCALE, true) &&
+                            extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
+                    extras.getInt(CropExtras.KEY_ASPECT_X, 0),
+                    extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
+                    extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
+                    extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
+                    (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
+                    extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
+                    extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
+                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
+                    extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
+        }
+        return null;
+    }
+
+    protected static CompressFormat convertExtensionToCompressFormat(String extension) {
+        return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
+    }
+
+    protected static String getFileExtension(String requestFormat) {
+        String outputFormat = (requestFormat == null)
+                ? "jpg"
+                : requestFormat;
+        outputFormat = outputFormat.toLowerCase();
+        return (outputFormat.equals("png") || outputFormat.equals("gif"))
+                ? "png" // We don't support gif compression.
+                : "jpg";
+    }
+
+    private RectF getBitmapCrop(RectF imageBounds) {
+        RectF crop = mCropView.getCrop();
+        RectF photo = mCropView.getPhoto();
+        if (crop == null || photo == null) {
+            Log.w(LOGTAG, "could not get crop");
+            return null;
+        }
+        RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
+        return scaledCrop;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
new file mode 100644
index 0000000..b0d324c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropDrawingUtils.java
@@ -0,0 +1,168 @@
+/*
+ * 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.gallery3d.filtershow.crop;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+
+public abstract class CropDrawingUtils {
+
+    public static void drawRuleOfThird(Canvas canvas, RectF bounds) {
+        Paint p = new Paint();
+        p.setStyle(Paint.Style.STROKE);
+        p.setColor(Color.argb(128, 255, 255, 255));
+        p.setStrokeWidth(2);
+        float stepX = bounds.width() / 3.0f;
+        float stepY = bounds.height() / 3.0f;
+        float x = bounds.left + stepX;
+        float y = bounds.top + stepY;
+        for (int i = 0; i < 2; i++) {
+            canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
+            x += stepX;
+        }
+        for (int j = 0; j < 2; j++) {
+            canvas.drawLine(bounds.left, y, bounds.right, y, p);
+            y += stepY;
+        }
+    }
+
+    public static void drawCropRect(Canvas canvas, RectF bounds) {
+        Paint p = new Paint();
+        p.setStyle(Paint.Style.STROKE);
+        p.setColor(Color.WHITE);
+        p.setStrokeWidth(3);
+        canvas.drawRect(bounds, p);
+    }
+
+    public static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
+            float centerX, float centerY) {
+        int left = (int) centerX - indicatorSize / 2;
+        int top = (int) centerY - indicatorSize / 2;
+        indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
+        indicator.draw(canvas);
+    }
+
+    public static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
+            RectF bounds, boolean fixedAspect, int selection) {
+        boolean notMoving = (selection == CropObject.MOVE_NONE);
+        if (fixedAspect) {
+            if ((selection == CropObject.TOP_LEFT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
+            }
+            if ((selection == CropObject.TOP_RIGHT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
+            }
+            if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
+            }
+            if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
+            }
+        } else {
+            if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
+            }
+            if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
+            }
+            if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
+            }
+            if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
+            }
+        }
+    }
+
+    public static void drawWallpaperSelectionFrame(Canvas canvas, RectF cropBounds, float spotX,
+            float spotY, Paint p, Paint shadowPaint) {
+        float sx = cropBounds.width() * spotX;
+        float sy = cropBounds.height() * spotY;
+        float cx = cropBounds.centerX();
+        float cy = cropBounds.centerY();
+        RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+        float temp = sx;
+        sx = sy;
+        sy = temp;
+        RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+        canvas.save();
+        canvas.clipRect(cropBounds);
+        canvas.clipRect(r1, Region.Op.DIFFERENCE);
+        canvas.clipRect(r2, Region.Op.DIFFERENCE);
+        canvas.drawPaint(shadowPaint);
+        canvas.restore();
+        Path path = new Path();
+        path.moveTo(r1.left, r1.top);
+        path.lineTo(r1.right, r1.top);
+        path.moveTo(r1.left, r1.top);
+        path.lineTo(r1.left, r1.bottom);
+        path.moveTo(r1.left, r1.bottom);
+        path.lineTo(r1.right, r1.bottom);
+        path.moveTo(r1.right, r1.top);
+        path.lineTo(r1.right, r1.bottom);
+        path.moveTo(r2.left, r2.top);
+        path.lineTo(r2.right, r2.top);
+        path.moveTo(r2.right, r2.top);
+        path.lineTo(r2.right, r2.bottom);
+        path.moveTo(r2.left, r2.bottom);
+        path.lineTo(r2.right, r2.bottom);
+        path.moveTo(r2.left, r2.top);
+        path.lineTo(r2.left, r2.bottom);
+        canvas.drawPath(path, p);
+    }
+
+    public static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds) {
+        canvas.drawRect(outerBounds.left, outerBounds.top, innerBounds.right, innerBounds.top, p);
+        canvas.drawRect(innerBounds.right, outerBounds.top, outerBounds.right, innerBounds.bottom,
+                p);
+        canvas.drawRect(innerBounds.left, innerBounds.bottom, outerBounds.right,
+                outerBounds.bottom, p);
+        canvas.drawRect(outerBounds.left, innerBounds.top, innerBounds.left, outerBounds.bottom, p);
+    }
+
+    public static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
+        Matrix m = new Matrix();
+        CropDrawingUtils.setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
+        return m;
+    }
+
+    public static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
+            RectF displayBounds) {
+        m.reset();
+        return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
+    }
+
+    public static boolean setImageToScreenMatrix(Matrix dst, RectF image,
+            RectF screen, int rotation) {
+        RectF rotatedImage = new RectF();
+        dst.setRotate(rotation, image.centerX(), image.centerY());
+        if (!dst.mapRect(rotatedImage, image)) {
+            return false; // fails for rotations that are not multiples of 90
+                          // degrees
+        }
+        boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER);
+        boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY());
+        return rToR && rot;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropExtras.java b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
new file mode 100644
index 0000000..60fe9af
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropExtras.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.crop;
+
+import android.net.Uri;
+
+public class CropExtras {
+
+    public static final String KEY_CROPPED_RECT = "cropped-rect";
+    public static final String KEY_OUTPUT_X = "outputX";
+    public static final String KEY_OUTPUT_Y = "outputY";
+    public static final String KEY_SCALE = "scale";
+    public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
+    public static final String KEY_ASPECT_X = "aspectX";
+    public static final String KEY_ASPECT_Y = "aspectY";
+    public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
+    public static final String KEY_RETURN_DATA = "return-data";
+    public static final String KEY_DATA = "data";
+    public static final String KEY_SPOTLIGHT_X = "spotlightX";
+    public static final String KEY_SPOTLIGHT_Y = "spotlightY";
+    public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
+    public static final String KEY_OUTPUT_FORMAT = "outputFormat";
+
+    private int mOutputX = 0;
+    private int mOutputY = 0;
+    private boolean mScaleUp = true;
+    private int mAspectX = 0;
+    private int mAspectY = 0;
+    private boolean mSetAsWallpaper = false;
+    private boolean mReturnData = false;
+    private Uri mExtraOutput = null;
+    private String mOutputFormat = null;
+    private boolean mShowWhenLocked = false;
+    private float mSpotlightX = 0;
+    private float mSpotlightY = 0;
+
+    public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY,
+            boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat,
+            boolean showWhenLocked, float spotlightX, float spotlightY) {
+        mOutputX = outputX;
+        mOutputY = outputY;
+        mScaleUp = scaleUp;
+        mAspectX = aspectX;
+        mAspectY = aspectY;
+        mSetAsWallpaper = setAsWallpaper;
+        mReturnData = returnData;
+        mExtraOutput = extraOutput;
+        mOutputFormat = outputFormat;
+        mShowWhenLocked = showWhenLocked;
+        mSpotlightX = spotlightX;
+        mSpotlightY = spotlightY;
+    }
+
+    public CropExtras(CropExtras c) {
+        this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper,
+                c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked,
+                c.mSpotlightX, c.mSpotlightY);
+    }
+
+    public int getOutputX() {
+        return mOutputX;
+    }
+
+    public int getOutputY() {
+        return mOutputY;
+    }
+
+    public boolean getScaleUp() {
+        return mScaleUp;
+    }
+
+    public int getAspectX() {
+        return mAspectX;
+    }
+
+    public int getAspectY() {
+        return mAspectY;
+    }
+
+    public boolean getSetAsWallpaper() {
+        return mSetAsWallpaper;
+    }
+
+    public boolean getReturnData() {
+        return mReturnData;
+    }
+
+    public Uri getExtraOutput() {
+        return mExtraOutput;
+    }
+
+    public String getOutputFormat() {
+        return mOutputFormat;
+    }
+
+    public boolean getShowWhenLocked() {
+        return mShowWhenLocked;
+    }
+
+    public float getSpotlightX() {
+        return mSpotlightX;
+    }
+
+    public float getSpotlightY() {
+        return mSpotlightY;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropLoader.java b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
new file mode 100644
index 0000000..430647e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropLoader.java
@@ -0,0 +1,302 @@
+/*
+ * 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.gallery3d.filtershow.crop;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * This class contains static methods for loading a bitmap and
+ * maintains no instance state.
+ */
+public abstract class CropLoader {
+    public static final String LOGTAG = "CropLoader";
+    public static final String JPEG_MIME_TYPE = "image/jpeg";
+
+    private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss";
+    public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
+
+    /**
+     * Returns the orientation of image at the given URI as one of 0, 90, 180,
+     * 270.
+     *
+     * @param uri URI of image to open.
+     * @param context context whose ContentResolver to use.
+     * @return the orientation of the image. Defaults to 0.
+     */
+    public static int getMetadataRotation(Uri uri, Context context) {
+        if (uri == null || context == null) {
+            throw new IllegalArgumentException("bad argument to getScaledBitmap");
+        }
+        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
+            String mimeType = context.getContentResolver().getType(uri);
+            if (mimeType != JPEG_MIME_TYPE) {
+                return 0;
+            }
+            String path = uri.getPath();
+            int orientation = 0;
+            ExifInterface exif = new ExifInterface();
+            try {
+                exif.readExif(path);
+                orientation = ExifInterface.getRotationForOrientationValue(
+                        exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue());
+            } catch (IOException e) {
+                Log.w(LOGTAG, "Failed to read EXIF orientation", e);
+            }
+            return orientation;
+        }
+        Cursor cursor = null;
+        try {
+            cursor = context.getContentResolver().query(uri,
+                    new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
+                    null, null, null);
+            if (cursor.moveToNext()) {
+                int ori = cursor.getInt(0);
+                return (ori < 0) ? 0 : ori;
+            }
+        } catch (SQLiteException e) {
+            return 0;
+        } catch (IllegalArgumentException e) {
+            return 0;
+        } finally {
+            Utils.closeSilently(cursor);
+        }
+        return 0;
+    }
+
+    /**
+     * Gets a bitmap at a given URI that is downsampled so that both sides are
+     * smaller than maxSideLength. The Bitmap's original dimensions are stored
+     * in the rect originalBounds.
+     *
+     * @param uri URI of image to open.
+     * @param context context whose ContentResolver to use.
+     * @param maxSideLength max side length of returned bitmap.
+     * @param originalBounds set to the actual bounds of the stored bitmap.
+     * @return downsampled bitmap or null if this operation failed.
+     */
+    public static Bitmap getConstrainedBitmap(Uri uri, Context context, int maxSideLength,
+            Rect originalBounds) {
+        if (maxSideLength <= 0 || originalBounds == null || uri == null || context == null) {
+            throw new IllegalArgumentException("bad argument to getScaledBitmap");
+        }
+        InputStream is = null;
+        try {
+            // Get width and height of stored bitmap
+            is = context.getContentResolver().openInputStream(uri);
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, options);
+            int w = options.outWidth;
+            int h = options.outHeight;
+            originalBounds.set(0, 0, w, h);
+
+            // If bitmap cannot be decoded, return null
+            if (w <= 0 || h <= 0) {
+                return null;
+            }
+
+            options = new BitmapFactory.Options();
+
+            // Find best downsampling size
+            int imageSide = Math.max(w, h);
+            options.inSampleSize = 1;
+            if (imageSide > maxSideLength) {
+                int shifts = 1 + Integer.numberOfLeadingZeros(maxSideLength)
+                        - Integer.numberOfLeadingZeros(imageSide);
+                options.inSampleSize <<= shifts;
+            }
+
+            // Make sure sample size is reasonable
+            if (options.inSampleSize <= 0 ||
+                    0 >= (int) (Math.min(w, h) / options.inSampleSize)) {
+                return null;
+            }
+
+            // Decode actual bitmap.
+            options.inMutable = true;
+            is.close();
+            is = context.getContentResolver().openInputStream(uri);
+            return BitmapFactory.decodeStream(is, null, options);
+        } catch (FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
+        } catch (IOException e) {
+            Log.e(LOGTAG, "IOException: " + uri, e);
+        } finally {
+            Utils.closeSilently(is);
+        }
+        return null;
+    }
+
+    /**
+     * Gets a bitmap that has been downsampled using sampleSize.
+     *
+     * @param uri URI of image to open.
+     * @param context context whose ContentResolver to use.
+     * @param sampleSize downsampling amount.
+     * @return downsampled bitmap.
+     */
+    public static Bitmap getBitmap(Uri uri, Context context, int sampleSize) {
+        if (uri == null || context == null) {
+            throw new IllegalArgumentException("bad argument to getScaledBitmap");
+        }
+        InputStream is = null;
+        try {
+            is = context.getContentResolver().openInputStream(uri);
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inMutable = true;
+            options.inSampleSize = sampleSize;
+            return BitmapFactory.decodeStream(is, null, options);
+        } catch (FileNotFoundException e) {
+            Log.e(LOGTAG, "FileNotFoundException: " + uri, e);
+        } finally {
+            Utils.closeSilently(is);
+        }
+        return null;
+    }
+
+    // TODO: Super gnarly (copied from SaveCopyTask.java), do cleanup.
+
+    public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
+        File saveDirectory = getSaveDirectory(context, sourceUri);
+        if ((saveDirectory == null) || !saveDirectory.canWrite()) {
+            saveDirectory = new File(Environment.getExternalStorageDirectory(),
+                    DEFAULT_SAVE_DIRECTORY);
+        }
+        // Create the directory if it doesn't exist
+        if (!saveDirectory.exists())
+            saveDirectory.mkdirs();
+        return saveDirectory;
+    }
+
+
+
+    public static String getNewFileName(long time) {
+        return new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
+    }
+
+    public static File getNewFile(Context context, Uri sourceUri, String filename) {
+        File saveDirectory = getFinalSaveDirectory(context, sourceUri);
+        return new File(saveDirectory, filename  + ".JPG");
+    }
+
+    private interface ContentResolverQueryCallback {
+
+        void onCursorResult(Cursor cursor);
+    }
+
+    private static void querySource(Context context, Uri sourceUri, String[] projection,
+            ContentResolverQueryCallback callback) {
+        ContentResolver contentResolver = context.getContentResolver();
+        Cursor cursor = null;
+        try {
+            cursor = contentResolver.query(sourceUri, projection, null, null,
+                    null);
+            if ((cursor != null) && cursor.moveToNext()) {
+                callback.onCursorResult(cursor);
+            }
+        } catch (Exception e) {
+            // Ignore error for lacking the data column from the source.
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private static File getSaveDirectory(Context context, Uri sourceUri) {
+        final File[] dir = new File[1];
+        querySource(context, sourceUri, new String[] {
+                ImageColumns.DATA }, new ContentResolverQueryCallback() {
+                    @Override
+                    public void onCursorResult(Cursor cursor) {
+                        dir[0] = new File(cursor.getString(0)).getParentFile();
+                    }
+                });
+        return dir[0];
+    }
+
+    public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName,
+            long time) {
+        time /= 1000;
+
+        final ContentValues values = new ContentValues();
+        values.put(Images.Media.TITLE, saveFileName);
+        values.put(Images.Media.DISPLAY_NAME, file.getName());
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATE_TAKEN, time);
+        values.put(Images.Media.DATE_MODIFIED, time);
+        values.put(Images.Media.DATE_ADDED, time);
+        values.put(Images.Media.ORIENTATION, 0);
+        values.put(Images.Media.DATA, file.getAbsolutePath());
+        values.put(Images.Media.SIZE, file.length());
+
+        final String[] projection = new String[] {
+                ImageColumns.DATE_TAKEN,
+                ImageColumns.LATITUDE, ImageColumns.LONGITUDE,
+        };
+        querySource(context, sourceUri, projection,
+                new ContentResolverQueryCallback() {
+
+                    @Override
+                    public void onCursorResult(Cursor cursor) {
+                        values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+
+                        double latitude = cursor.getDouble(1);
+                        double longitude = cursor.getDouble(2);
+                        // TODO: Change || to && after the default location
+                        // issue is fixed.
+                        if ((latitude != 0f) || (longitude != 0f)) {
+                            values.put(Images.Media.LATITUDE, latitude);
+                            values.put(Images.Media.LONGITUDE, longitude);
+                        }
+                    }
+                });
+
+        return context.getContentResolver().insert(
+                Images.Media.EXTERNAL_CONTENT_URI, values);
+    }
+
+    public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
+        long time = System.currentTimeMillis();
+        String filename = getNewFileName(time);
+        File file = getNewFile(context, sourceUri, filename);
+        return insertContent(context, sourceUri, file, filename, time);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java
new file mode 100644
index 0000000..849ac60
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.crop;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
+import java.util.Arrays;
+
+public class CropMath {
+
+    /**
+     * Gets a float array of the 2D coordinates representing a rectangles
+     * corners.
+     * The order of the corners in the float array is:
+     * 0------->1
+     * ^        |
+     * |        v
+     * 3<-------2
+     *
+     * @param r  the rectangle to get the corners of
+     * @return  the float array of corners (8 floats)
+     */
+
+    public static float[] getCornersFromRect(RectF r) {
+        float[] corners = {
+                r.left, r.top,
+                r.right, r.top,
+                r.right, r.bottom,
+                r.left, r.bottom
+        };
+        return corners;
+    }
+
+    /**
+     * Returns true iff point (x, y) is within or on the rectangle's bounds.
+     * RectF's "contains" function treats points on the bottom and right bound
+     * as not being contained.
+     *
+     * @param r the rectangle
+     * @param x the x value of the point
+     * @param y the y value of the point
+     * @return
+     */
+    public static boolean inclusiveContains(RectF r, float x, float y) {
+        return !(x > r.right || x < r.left || y > r.bottom || y < r.top);
+    }
+
+    /**
+     * Takes an array of 2D coordinates representing corners and returns the
+     * smallest rectangle containing those coordinates.
+     *
+     * @param array array of 2D coordinates
+     * @return smallest rectangle containing coordinates
+     */
+    public static RectF trapToRect(float[] array) {
+        RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
+                Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+        for (int i = 1; i < array.length; i += 2) {
+            float x = array[i - 1];
+            float y = array[i];
+            r.left = (x < r.left) ? x : r.left;
+            r.top = (y < r.top) ? y : r.top;
+            r.right = (x > r.right) ? x : r.right;
+            r.bottom = (y > r.bottom) ? y : r.bottom;
+        }
+        r.sort();
+        return r;
+    }
+
+    /**
+     * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
+     * image bound rectangle, clamps it to the edge of the rectangle.
+     *
+     * @param imageBound the rectangle to clamp edge points to.
+     * @param array an array of points to clamp to the rectangle, gets set to
+     *            the clamped values.
+     */
+    public static void getEdgePoints(RectF imageBound, float[] array) {
+        if (array.length < 2)
+            return;
+        for (int x = 0; x < array.length; x += 2) {
+            array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right);
+            array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom);
+        }
+    }
+
+    /**
+     * Takes a point and the corners of a rectangle and returns the two corners
+     * representing the side of the rectangle closest to the point.
+     *
+     * @param point the point which is being checked
+     * @param corners the corners of the rectangle
+     * @return two corners representing the side of the rectangle
+     */
+    public static float[] closestSide(float[] point, float[] corners) {
+        int len = corners.length;
+        float oldMag = Float.POSITIVE_INFINITY;
+        float[] bestLine = null;
+        for (int i = 0; i < len; i += 2) {
+            float[] line = {
+                    corners[i], corners[(i + 1) % len],
+                    corners[(i + 2) % len], corners[(i + 3) % len]
+            };
+            float mag = GeometryMath.vectorLength(
+                    GeometryMath.shortestVectorFromPointToLine(point, line));
+            if (mag < oldMag) {
+                oldMag = mag;
+                bestLine = line;
+            }
+        }
+        return bestLine;
+    }
+
+    /**
+     * Checks if a given point is within a rotated rectangle.
+     *
+     * @param point 2D point to check
+     * @param bound rectangle to rotate
+     * @param rot angle of rotation about rectangle center
+     * @return true if point is within rotated rectangle
+     */
+    public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) {
+        Matrix m = new Matrix();
+        float[] p = Arrays.copyOf(point, 2);
+        m.setRotate(rot, bound.centerX(), bound.centerY());
+        Matrix m0 = new Matrix();
+        if (!m.invert(m0))
+            return false;
+        m0.mapPoints(p);
+        return inclusiveContains(bound, p[0], p[1]);
+    }
+
+    /**
+     * Checks if a given point is within a rotated rectangle.
+     *
+     * @param point 2D point to check
+     * @param rotatedRect corners of a rotated rectangle
+     * @param center center of the rotated rectangle
+     * @return true if point is within rotated rectangle
+     */
+    public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) {
+        RectF unrotated = new RectF();
+        float angle = getUnrotated(rotatedRect, center, unrotated);
+        return pointInRotatedRect(point, unrotated, angle);
+    }
+
+    /**
+     * Resizes rectangle to have a certain aspect ratio (center remains
+     * stationary).
+     *
+     * @param r rectangle to resize
+     * @param w new width aspect
+     * @param h new height aspect
+     */
+    public static void fixAspectRatio(RectF r, float w, float h) {
+        float scale = Math.min(r.width() / w, r.height() / h);
+        float centX = r.centerX();
+        float centY = r.centerY();
+        float hw = scale * w / 2;
+        float hh = scale * h / 2;
+        r.set(centX - hw, centY - hh, centX + hw, centY + hh);
+    }
+
+    /**
+     * Resizes rectangle to have a certain aspect ratio (center remains
+     * stationary) while constraining it to remain within the original rect.
+     *
+     * @param r rectangle to resize
+     * @param w new width aspect
+     * @param h new height aspect
+     */
+    public static void fixAspectRatioContained(RectF r, float w, float h) {
+        float origW = r.width();
+        float origH = r.height();
+        float origA = origW / origH;
+        float a = w / h;
+        float finalW = origW;
+        float finalH = origH;
+        if (origA < a) {
+            finalH = origW / a;
+        } else {
+            finalW = origH * a;
+        }
+        float centX = r.centerX();
+        float centY = r.centerY();
+        float hw = finalW / 2;
+        float hh = finalH / 2;
+        r.set(centX - hw, centY - hh, centX + hw, centY + hh);
+    }
+
+    /**
+     * Stretches/Scales/Translates photoBounds to match displayBounds, and
+     * and returns an equivalent stretched/scaled/translated cropBounds or null
+     * if the mapping is invalid.
+     * @param cropBounds  cropBounds to transform
+     * @param photoBounds  original bounds containing crop bounds
+     * @param displayBounds  final bounds for crop
+     * @return  the stretched/scaled/translated crop bounds that fit within displayBounds
+     */
+    public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds,
+            RectF displayBounds) {
+        Matrix m = new Matrix();
+        m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL);
+        RectF trueCrop = new RectF(cropBounds);
+        if (!m.mapRect(trueCrop)) {
+            return null;
+        }
+        return trueCrop;
+    }
+
+    /**
+     * Returns the size of a bitmap in bytes.
+     * @param bmap  bitmap whose size to check
+     * @return  bitmap size in bytes
+     */
+    public static int getBitmapSize(Bitmap bmap) {
+        return bmap.getRowBytes() * bmap.getHeight();
+    }
+
+    /**
+     * Constrains rotation to be in [0, 90, 180, 270] rounding down.
+     * @param rotation  any rotation value, in degrees
+     * @return  integer rotation in [0, 90, 180, 270]
+     */
+    public static int constrainedRotation(float rotation) {
+        int r = (int) ((rotation % 360) / 90);
+        r = (r < 0) ? (r + 4) : r;
+        return r * 90;
+    }
+
+    private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
+        float dy = rotatedRect[1] - rotatedRect[3];
+        float dx = rotatedRect[0] - rotatedRect[2];
+        float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI);
+        Matrix m = new Matrix();
+        m.setRotate(-angle, center[0], center[1]);
+        float[] unrotatedRect = new float[rotatedRect.length];
+        m.mapPoints(unrotatedRect, rotatedRect);
+        unrotated.set(trapToRect(unrotatedRect));
+        return angle;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropObject.java b/src/com/android/gallery3d/filtershow/crop/CropObject.java
new file mode 100644
index 0000000..bea3ffa
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropObject.java
@@ -0,0 +1,331 @@
+/*
+ * 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.gallery3d.filtershow.crop;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.gallery3d.filtershow.imageshow.GeometryMath;
+
+public class CropObject {
+
+    private BoundedRect mBoundedRect;
+    private float mAspectWidth = 1;
+    private float mAspectHeight = 1;
+    private boolean mFixAspectRatio = false;
+    private float mRotation = 0;
+    private float mTouchTolerance = 45;
+    private float mMinSideSize = 20;
+
+    public static final int MOVE_NONE = 0;
+    // Sides
+    public static final int MOVE_LEFT = 1;
+    public static final int MOVE_TOP = 2;
+    public static final int MOVE_RIGHT = 4;
+    public static final int MOVE_BOTTOM = 8;
+    public static final int MOVE_BLOCK = 16;
+
+    // Corners
+    public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
+    public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
+    public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
+    public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
+
+    private int mMovingEdges = MOVE_NONE;
+
+    public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
+        mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+    }
+
+    public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
+        mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
+    }
+
+    public void resetBoundsTo(RectF inner, RectF outer) {
+        mBoundedRect.resetTo(0, outer, inner);
+    }
+
+    public void getInnerBounds(RectF r) {
+        mBoundedRect.setToInner(r);
+    }
+
+    public void getOuterBounds(RectF r) {
+        mBoundedRect.setToOuter(r);
+    }
+
+    public RectF getInnerBounds() {
+        return mBoundedRect.getInner();
+    }
+
+    public RectF getOuterBounds() {
+        return mBoundedRect.getOuter();
+    }
+
+    public int getSelectState() {
+        return mMovingEdges;
+    }
+
+    public boolean isFixedAspect() {
+        return mFixAspectRatio;
+    }
+
+    public void rotateOuter(int angle) {
+        mRotation = angle % 360;
+        mBoundedRect.setRotation(mRotation);
+        clearSelectState();
+    }
+
+    public boolean setInnerAspectRatio(float width, float height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Width and Height must be greater than zero");
+        }
+        RectF inner = mBoundedRect.getInner();
+        CropMath.fixAspectRatioContained(inner, width, height);
+        if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
+            return false;
+        }
+        mAspectWidth = width;
+        mAspectHeight = height;
+        mFixAspectRatio = true;
+        mBoundedRect.setInner(inner);
+        clearSelectState();
+        return true;
+    }
+
+    public void setTouchTolerance(float tolerance) {
+        if (tolerance <= 0) {
+            throw new IllegalArgumentException("Tolerance must be greater than zero");
+        }
+        mTouchTolerance = tolerance;
+    }
+
+    public void setMinInnerSideSize(float minSide) {
+        if (minSide <= 0) {
+            throw new IllegalArgumentException("Min dide must be greater than zero");
+        }
+        mMinSideSize = minSide;
+    }
+
+    public void unsetAspectRatio() {
+        mFixAspectRatio = false;
+        clearSelectState();
+    }
+
+    public boolean hasSelectedEdge() {
+        return mMovingEdges != MOVE_NONE;
+    }
+
+    public static boolean checkCorner(int selected) {
+        return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
+                || selected == BOTTOM_LEFT;
+    }
+
+    public static boolean checkEdge(int selected) {
+        return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
+                || selected == MOVE_BOTTOM;
+    }
+
+    public static boolean checkBlock(int selected) {
+        return selected == MOVE_BLOCK;
+    }
+
+    public static boolean checkValid(int selected) {
+        return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
+                || checkCorner(selected);
+    }
+
+    public void clearSelectState() {
+        mMovingEdges = MOVE_NONE;
+    }
+
+    public int wouldSelectEdge(float x, float y) {
+        int edgeSelected = calculateSelectedEdge(x, y);
+        if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
+            return edgeSelected;
+        }
+        return MOVE_NONE;
+    }
+
+    public boolean selectEdge(int edge) {
+        if (!checkValid(edge)) {
+            // temporary
+            throw new IllegalArgumentException("bad edge selected");
+            // return false;
+        }
+        if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
+            // temporary
+            throw new IllegalArgumentException("bad corner selected");
+            // return false;
+        }
+        mMovingEdges = edge;
+        return true;
+    }
+
+    public boolean selectEdge(float x, float y) {
+        int edgeSelected = calculateSelectedEdge(x, y);
+        if (mFixAspectRatio) {
+            edgeSelected = fixEdgeToCorner(edgeSelected);
+        }
+        if (edgeSelected == MOVE_NONE) {
+            return false;
+        }
+        return selectEdge(edgeSelected);
+    }
+
+    public boolean moveCurrentSelection(float dX, float dY) {
+        if (mMovingEdges == MOVE_NONE) {
+            return false;
+        }
+        RectF crop = mBoundedRect.getInner();
+
+        float minWidthHeight = mMinSideSize;
+
+        int movingEdges = mMovingEdges;
+        if (movingEdges == MOVE_BLOCK) {
+            mBoundedRect.moveInner(dX, dY);
+            return true;
+        } else {
+            float dx = 0;
+            float dy = 0;
+
+            if ((movingEdges & MOVE_LEFT) != 0) {
+                dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
+            }
+            if ((movingEdges & MOVE_TOP) != 0) {
+                dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
+            }
+            if ((movingEdges & MOVE_RIGHT) != 0) {
+                dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
+                        - crop.right;
+            }
+            if ((movingEdges & MOVE_BOTTOM) != 0) {
+                dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
+                        - crop.bottom;
+            }
+
+            if (mFixAspectRatio) {
+                float[] l1 = {
+                        crop.left, crop.bottom
+                };
+                float[] l2 = {
+                        crop.right, crop.top
+                };
+                if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
+                    l1[1] = crop.top;
+                    l2[1] = crop.bottom;
+                }
+                float[] b = {
+                        l1[0] - l2[0], l1[1] - l2[1]
+                };
+                float[] disp = {
+                        dx, dy
+                };
+                float[] bUnit = GeometryMath.normalize(b);
+                float sp = GeometryMath.scalarProjection(disp, bUnit);
+                dx = sp * bUnit[0];
+                dy = sp * bUnit[1];
+                RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
+
+                mBoundedRect.fixedAspectResizeInner(newCrop);
+            } else {
+                if ((movingEdges & MOVE_LEFT) != 0) {
+                    crop.left += dx;
+                }
+                if ((movingEdges & MOVE_TOP) != 0) {
+                    crop.top += dy;
+                }
+                if ((movingEdges & MOVE_RIGHT) != 0) {
+                    crop.right += dx;
+                }
+                if ((movingEdges & MOVE_BOTTOM) != 0) {
+                    crop.bottom += dy;
+                }
+                mBoundedRect.resizeInner(crop);
+            }
+        }
+        return true;
+    }
+
+    // Helper methods
+
+    private int calculateSelectedEdge(float x, float y) {
+        RectF cropped = mBoundedRect.getInner();
+
+        float left = Math.abs(x - cropped.left);
+        float right = Math.abs(x - cropped.right);
+        float top = Math.abs(y - cropped.top);
+        float bottom = Math.abs(y - cropped.bottom);
+
+        int edgeSelected = MOVE_NONE;
+        // Check left or right.
+        if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+                && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
+            edgeSelected |= MOVE_LEFT;
+        }
+        else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+                && ((y - mTouchTolerance) <= cropped.bottom)) {
+            edgeSelected |= MOVE_RIGHT;
+        }
+
+        // Check top or bottom.
+        if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+                && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
+            edgeSelected |= MOVE_TOP;
+        }
+        else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+                && ((x - mTouchTolerance) <= cropped.right)) {
+            edgeSelected |= MOVE_BOTTOM;
+        }
+        return edgeSelected;
+    }
+
+    private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
+        RectF newCrop = null;
+        // Fix opposite corner in place and move sides
+        if (moving_corner == BOTTOM_RIGHT) {
+            newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
+                    + dy);
+        } else if (moving_corner == BOTTOM_LEFT) {
+            newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
+                    + dy);
+        } else if (moving_corner == TOP_LEFT) {
+            newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
+                    r.right, r.bottom);
+        } else if (moving_corner == TOP_RIGHT) {
+            newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
+                    + r.width() + dx, r.bottom);
+        }
+        return newCrop;
+    }
+
+    private static int fixEdgeToCorner(int moving_edges) {
+        if (moving_edges == MOVE_LEFT) {
+            moving_edges |= MOVE_TOP;
+        }
+        if (moving_edges == MOVE_TOP) {
+            moving_edges |= MOVE_LEFT;
+        }
+        if (moving_edges == MOVE_RIGHT) {
+            moving_edges |= MOVE_BOTTOM;
+        }
+        if (moving_edges == MOVE_BOTTOM) {
+            moving_edges |= MOVE_RIGHT;
+        }
+        return moving_edges;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropView.java b/src/com/android/gallery3d/filtershow/crop/CropView.java
new file mode 100644
index 0000000..87e4542
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/crop/CropView.java
@@ -0,0 +1,363 @@
+/*
+ * 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.gallery3d.filtershow.crop;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.DashPathEffect;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.gallery3d.R;
+
+
+public class CropView extends View {
+    private static final String LOGTAG = "CropView";
+
+    private RectF mImageBounds = new RectF();
+    private RectF mScreenBounds = new RectF();
+    private RectF mScreenImageBounds = new RectF();
+    private RectF mScreenCropBounds = new RectF();
+    private Rect mShadowBounds = new Rect();
+
+    private Bitmap mBitmap;
+    private Paint mPaint = new Paint();
+
+    private NinePatchDrawable mShadow;
+    private CropObject mCropObj = null;
+    private final Drawable mCropIndicator;
+    private final int mIndicatorSize;
+    private int mRotation = 0;
+    private boolean mMovingBlock = false;
+    private Matrix mDisplayMatrix = null;
+    private Matrix mDisplayMatrixInverse = null;
+    private boolean mDirty = false;
+
+    private float mPrevX = 0;
+    private float mPrevY = 0;
+    private float mSpotX = 0;
+    private float mSpotY = 0;
+    private boolean mDoSpot = false;
+
+    private int mShadowMargin = 15;
+    private int mMargin = 32;
+    private int mOverlayShadowColor = 0xCF000000;
+    private int mOverlayWPShadowColor = 0x5F000000;
+    private int mWPMarkerColor = 0x7FFFFFFF;
+    private int mMinSideSize = 90;
+    private int mTouchTolerance = 40;
+    private float mDashOnLength = 20;
+    private float mDashOffLength = 10;
+
+    private enum Mode {
+        NONE, MOVE
+    }
+
+    private Mode mState = Mode.NONE;
+
+    public CropView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        Resources rsc = context.getResources();
+        mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
+        mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
+        mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
+        mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
+        mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
+        mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
+        mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
+        mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
+        mOverlayWPShadowColor = (int) rsc.getColor(R.color.crop_shadow_wp_color);
+        mWPMarkerColor = (int) rsc.getColor(R.color.crop_wp_markers);
+        mDashOnLength = rsc.getDimension(R.dimen.wp_selector_dash_length);
+        mDashOffLength = rsc.getDimension(R.dimen.wp_selector_off_length);
+    }
+
+    public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation) {
+        mBitmap = image;
+        if (mCropObj != null) {
+            RectF crop = mCropObj.getInnerBounds();
+            RectF containing = mCropObj.getOuterBounds();
+            if (crop != newCropBounds || containing != newPhotoBounds
+                    || mRotation != rotation) {
+                mRotation = rotation;
+                mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
+                clearDisplay();
+            }
+        } else {
+            mRotation = rotation;
+            mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
+            clearDisplay();
+        }
+    }
+
+    public RectF getCrop() {
+        return mCropObj.getInnerBounds();
+    }
+
+    public RectF getPhoto() {
+        return mCropObj.getOuterBounds();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        float x = event.getX();
+        float y = event.getY();
+        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+            return true;
+        }
+        float[] touchPoint = {
+                x, y
+        };
+        mDisplayMatrixInverse.mapPoints(touchPoint);
+        x = touchPoint[0];
+        y = touchPoint[1];
+        switch (event.getActionMasked()) {
+            case (MotionEvent.ACTION_DOWN):
+                if (mState == Mode.NONE) {
+                    if (!mCropObj.selectEdge(x, y)) {
+                        mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
+                    }
+                    mPrevX = x;
+                    mPrevY = y;
+                    mState = Mode.MOVE;
+                }
+                break;
+            case (MotionEvent.ACTION_UP):
+                if (mState == Mode.MOVE) {
+                    mCropObj.selectEdge(CropObject.MOVE_NONE);
+                    mMovingBlock = false;
+                    mPrevX = x;
+                    mPrevY = y;
+                    mState = Mode.NONE;
+                }
+                break;
+            case (MotionEvent.ACTION_MOVE):
+                if (mState == Mode.MOVE) {
+                    float dx = x - mPrevX;
+                    float dy = y - mPrevY;
+                    mCropObj.moveCurrentSelection(dx, dy);
+                    mPrevX = x;
+                    mPrevY = y;
+                }
+                break;
+            default:
+                break;
+        }
+        invalidate();
+        return true;
+    }
+
+    private void reset() {
+        Log.w(LOGTAG, "crop reset called");
+        mState = Mode.NONE;
+        mCropObj = null;
+        mRotation = 0;
+        mMovingBlock = false;
+        clearDisplay();
+    }
+
+    private void clearDisplay() {
+        mDisplayMatrix = null;
+        mDisplayMatrixInverse = null;
+        invalidate();
+    }
+
+    protected void configChanged() {
+        mDirty = true;
+    }
+
+    public void applyFreeAspect() {
+        mCropObj.unsetAspectRatio();
+        invalidate();
+    }
+
+    public void applyOriginalAspect() {
+        RectF outer = mCropObj.getOuterBounds();
+        float w = outer.width();
+        float h = outer.height();
+        if (w > 0 && h > 0) {
+            applyAspect(w, h);
+            mCropObj.resetBoundsTo(outer, outer);
+        } else {
+            Log.w(LOGTAG, "failed to set aspect ratio original");
+        }
+    }
+
+    public void applySquareAspect() {
+        applyAspect(1, 1);
+    }
+
+    public void applyAspect(float x, float y) {
+        if (x <= 0 || y <= 0) {
+            throw new IllegalArgumentException("Bad arguments to applyAspect");
+        }
+        // If we are rotated by 90 degrees from horizontal, swap x and y
+        if (((mRotation < 0) ? -mRotation : mRotation) % 180 == 90) {
+            float tmp = x;
+            x = y;
+            y = tmp;
+        }
+        if (!mCropObj.setInnerAspectRatio(x, y)) {
+            Log.w(LOGTAG, "failed to set aspect ratio");
+        }
+        invalidate();
+    }
+
+    public void setWallpaperSpotlight(float spotlightX, float spotlightY) {
+        mSpotX = spotlightX;
+        mSpotY = spotlightY;
+        if (mSpotX > 0 && mSpotY > 0) {
+            mDoSpot = true;
+        }
+    }
+
+    public void unsetWallpaperSpotlight() {
+        mDoSpot = false;
+    }
+
+    /**
+     * Rotates first d bits in integer x to the left some number of times.
+     */
+    private int bitCycleLeft(int x, int times, int d) {
+        int mask = (1 << d) - 1;
+        int mout = x & mask;
+        times %= d;
+        int hi = mout >> (d - times);
+        int low = (mout << times) & mask;
+        int ret = x & ~mask;
+        ret |= low;
+        ret |= hi;
+        return ret;
+    }
+
+    /**
+     * Find the selected edge or corner in screen coordinates.
+     */
+    private int decode(int movingEdges, float rotation) {
+        int rot = CropMath.constrainedRotation(rotation);
+        switch (rot) {
+            case 90:
+                return bitCycleLeft(movingEdges, 1, 4);
+            case 180:
+                return bitCycleLeft(movingEdges, 2, 4);
+            case 270:
+                return bitCycleLeft(movingEdges, 3, 4);
+            default:
+                return movingEdges;
+        }
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        if (mBitmap == null) {
+            return;
+        }
+        if (mDirty) {
+            mDirty = false;
+            clearDisplay();
+        }
+
+        mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+        mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
+        mScreenBounds.inset(mMargin, mMargin);
+
+        // If crop object doesn't exist, create it and update it from master
+        // state
+        if (mCropObj == null) {
+            reset();
+            mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
+        }
+
+        // If display matrix doesn't exist, create it and its dependencies
+        if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
+            mDisplayMatrix = new Matrix();
+            mDisplayMatrix.reset();
+            if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
+                    mRotation)) {
+                Log.w(LOGTAG, "failed to get screen matrix");
+                mDisplayMatrix = null;
+                return;
+            }
+            mDisplayMatrixInverse = new Matrix();
+            mDisplayMatrixInverse.reset();
+            if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
+                Log.w(LOGTAG, "could not invert display matrix");
+                mDisplayMatrixInverse = null;
+                return;
+            }
+            // Scale min side and tolerance by display matrix scale factor
+            mCropObj.setMinInnerSideSize(mDisplayMatrixInverse.mapRadius(mMinSideSize));
+            mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
+        }
+
+        mScreenImageBounds.set(mImageBounds);
+
+        // Draw background shadow
+        if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
+            int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
+            mScreenImageBounds.roundOut(mShadowBounds);
+            mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
+                    margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
+            mShadow.setBounds(mShadowBounds);
+            mShadow.draw(canvas);
+        }
+
+        // Draw actual bitmap
+        canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
+
+        mCropObj.getInnerBounds(mScreenCropBounds);
+
+        if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
+
+            // Draw overlay shadows
+            Paint p = new Paint();
+            p.setColor(mOverlayShadowColor);
+            p.setStyle(Paint.Style.FILL);
+            CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
+
+            // Draw crop rect and markers
+            CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
+            if (!mDoSpot) {
+                CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
+            } else {
+                Paint wpPaint = new Paint();
+                wpPaint.setColor(mWPMarkerColor);
+                wpPaint.setStrokeWidth(3);
+                wpPaint.setStyle(Paint.Style.STROKE);
+                wpPaint.setPathEffect(new DashPathEffect(new float[]
+                        {mDashOnLength, mDashOnLength + mDashOffLength}, 0));
+                p.setColor(mOverlayWPShadowColor);
+                CropDrawingUtils.drawWallpaperSelectionFrame(canvas, mScreenCropBounds,
+                        mSpotX, mSpotY, wpPaint, p);
+            }
+            CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
+                    mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mRotation));
+        }
+
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
new file mode 100644
index 0000000..af694d8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
@@ -0,0 +1,138 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+
+/**
+ * The basic editor that all the one parameter filters
+ */
+public class BasicEditor extends ParametricEditor implements ParameterInteger {
+    public static int ID = R.id.basicEditor;
+    private final String LOGTAG = "BasicEditor";
+
+    public BasicEditor() {
+        super(ID, R.layout.filtershow_default_editor, R.id.basicEditor);
+    }
+
+    protected BasicEditor(int id) {
+        super(id, R.layout.filtershow_default_editor, R.id.basicEditor);
+    }
+
+    protected BasicEditor(int id, int layoutID, int viewID) {
+        super(id, layoutID, viewID);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        if (getLocalRepresentation() != null && getLocalRepresentation() instanceof FilterBasicRepresentation) {
+            FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+            updateText();
+        }
+    }
+
+    private FilterBasicRepresentation getBasicRepresentation() {
+        FilterRepresentation tmpRep = getLocalRepresentation();
+        if (tmpRep != null && tmpRep instanceof FilterBasicRepresentation) {
+            return (FilterBasicRepresentation) tmpRep;
+
+        }
+        return null;
+    }
+
+    @Override
+    public int getMaximum() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getMaximum();
+    }
+
+    @Override
+    public int getMinimum() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getMinimum();
+    }
+
+    @Override
+    public int getDefaultValue() {
+        return 0;
+    }
+
+    @Override
+    public int getValue() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getValue();
+    }
+
+    @Override
+    public String getValueString() {
+        return null;
+    }
+
+    @Override
+    public void setValue(int value) {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return;
+        }
+        rep.setValue(value);
+        commitLocalRepresentation();
+    }
+
+    @Override
+    public String getParameterName() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        return mContext.getString(rep.getTextId());
+    }
+
+    @Override
+    public String getParameterType() {
+        return sParameterType;
+    }
+
+    @Override
+    public void setController(Control c) {
+    }
+
+    @Override
+    public void setFilterView(FilterView editor) {
+
+    }
+
+    @Override
+    public void copyFrom(Parameter src) {
+
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/Editor.java b/src/com/android/gallery3d/filtershow/editors/Editor.java
new file mode 100644
index 0000000..02bbcde
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/Editor.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+/**
+ * Base class for Editors Must contain a mImageShow and a top level view
+ */
+public class Editor implements OnSeekBarChangeListener, SwapButton.SwapButtonListener {
+    protected Context mContext;
+    protected View mView;
+    protected ImageShow mImageShow;
+    protected FrameLayout mFrameLayout;
+    protected SeekBar mSeekBar;
+    Button mEditTitle;
+    protected Button mFilterTitle;
+    protected int mID;
+    private final String LOGTAG = "Editor";
+    protected FilterRepresentation mLocalRepresentation = null;
+    protected byte mShowParameter = SHOW_VALUE_UNDEFINED;
+    private Button mButton;
+    public static byte SHOW_VALUE_UNDEFINED = -1;
+    public static byte SHOW_VALUE_OFF = 0;
+    public static byte SHOW_VALUE_INT = 1;
+
+    public static void hackFixStrings(Menu menu) {
+        int count = menu.size();
+        for (int i = 0; i < count; i++) {
+            MenuItem item = menu.getItem(i);
+            item.setTitle(item.getTitle().toString().toUpperCase());
+        }
+    }
+
+    public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+        return effectName.toUpperCase() + " " + parameterValue;
+    }
+
+    protected Editor(int id) {
+        mID = id;
+    }
+
+    public int getID() {
+        return mID;
+    }
+
+    public byte showParameterValue() {
+        return mShowParameter;
+    }
+
+    public boolean showsSeekBar() {
+        return true;
+    }
+
+    public void setUpEditorUI(View actionButton, View editControl,
+                              Button editTitle, Button stateButton) {
+        mEditTitle = editTitle;
+        mFilterTitle = stateButton;
+        mButton = editTitle;
+        setMenuIcon(true);
+        setUtilityPanelUI(actionButton, editControl);
+    }
+
+    public boolean showsPopupIndicator() {
+        return true;
+    }
+
+    /**
+     * @param actionButton the would be the area for menu etc
+     * @param editControl this is the black area for sliders etc
+     */
+    public void setUtilityPanelUI(View actionButton, View editControl) {
+
+        AttributeSet aset;
+        Context context = editControl.getContext();
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        LinearLayout lp = (LinearLayout) inflater.inflate(
+                R.layout.filtershow_seekbar, (ViewGroup) editControl, true);
+        mSeekBar = (SeekBar) lp.findViewById(R.id.primarySeekBar);
+        mSeekBar.setOnSeekBarChangeListener(this);
+
+        if (showsSeekBar()) {
+            mSeekBar.setOnSeekBarChangeListener(this);
+            mSeekBar.setVisibility(View.VISIBLE);
+        } else {
+            mSeekBar.setVisibility(View.INVISIBLE);
+        }
+
+        if (mButton != null) {
+            if (showsPopupIndicator()) {
+                mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0,
+                        R.drawable.filtershow_menu_marker, 0);
+            } else {
+                mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
+            }
+        }
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
+
+    }
+
+    public void setPanel() {
+
+    }
+
+    public void createEditor(Context context,FrameLayout frameLayout) {
+        mContext = context;
+        mFrameLayout = frameLayout;
+        mLocalRepresentation = null;
+    }
+
+    protected void unpack(int viewid, int layoutid) {
+
+        if (mView == null) {
+            mView = mFrameLayout.findViewById(viewid);
+            if (mView == null) {
+                LayoutInflater inflater = (LayoutInflater) mContext.getSystemService
+                        (Context.LAYOUT_INFLATER_SERVICE);
+                mView = inflater.inflate(layoutid, mFrameLayout, false);
+                mFrameLayout.addView(mView, mView.getLayoutParams());
+            }
+        }
+        mImageShow = findImageShow(mView);
+    }
+
+    private ImageShow findImageShow(View view) {
+        if (view instanceof ImageShow) {
+            return (ImageShow) view;
+        }
+        if (!(view instanceof ViewGroup)) {
+            return null;
+        }
+        ViewGroup vg = (ViewGroup) view;
+        int n = vg.getChildCount();
+        for (int i = 0; i < n; i++) {
+            View v = vg.getChildAt(i);
+            if (v instanceof ImageShow) {
+                return (ImageShow) v;
+            } else if (v instanceof ViewGroup) {
+                return findImageShow(v);
+            }
+        }
+        return null;
+    }
+
+    public View getTopLevelView() {
+        return mView;
+    }
+
+    public ImageShow getImageShow() {
+        return mImageShow;
+    }
+
+    public void setImageLoader(ImageLoader imageLoader) {
+        mImageShow.setImageLoader(imageLoader);
+    }
+
+    public void setVisibility(int visible) {
+        mView.setVisibility(visible);
+    }
+
+    public FilterRepresentation getLocalRepresentation() {
+        if (mLocalRepresentation == null) {
+            ImagePreset preset = MasterImage.getImage().getPreset();
+            FilterRepresentation filterRepresentation = MasterImage.getImage().getCurrentFilterRepresentation();
+            mLocalRepresentation = preset.getFilterRepresentationCopyFrom(filterRepresentation);
+            if (mShowParameter == SHOW_VALUE_UNDEFINED) {
+                boolean show = filterRepresentation.showParameterValue();
+                mShowParameter = show ? SHOW_VALUE_INT : SHOW_VALUE_OFF;
+            }
+
+        }
+        return mLocalRepresentation;
+    }
+
+    public void commitLocalRepresentation() {
+        ImagePreset preset = MasterImage.getImage().getPreset();
+        preset.updateFilterRepresentation(getLocalRepresentation());
+        if (mButton != null) {
+            updateText();
+        }
+    }
+
+    protected void updateText() {
+        String s = "";
+        if (mLocalRepresentation != null) {
+            s = mContext.getString(mLocalRepresentation.getTextId());
+        }
+        mButton.setText(calculateUserMessage(mContext, s, ""));
+    }
+
+    /**
+     * called after the filter is set and the select is called
+     */
+    public void reflectCurrentFilter() {
+        mLocalRepresentation = null;
+        FilterRepresentation representation = getLocalRepresentation();
+        if (representation != null && mFilterTitle != null && representation.getTextId() != 0) {
+            String text = mContext.getString(representation.getTextId()).toUpperCase();
+            mFilterTitle.setText(text);
+            updateText();
+        }
+    }
+
+    public boolean useUtilityPanel() {
+        return true;
+    }
+
+    public void openUtilityPanel(LinearLayout mAccessoryViewList) {
+        setMenuIcon(false);
+        if (mImageShow != null) {
+            mImageShow.openUtilityPanel(mAccessoryViewList);
+        }
+    }
+
+    protected void setMenuIcon(boolean on) {
+        mEditTitle.setCompoundDrawablesRelativeWithIntrinsicBounds(
+                0, 0, on ? R.drawable.filtershow_menu_marker : 0, 0);
+    }
+
+    protected void createMenu(int[] strId, View button) {
+        PopupMenu pmenu = new PopupMenu(mContext, button);
+        Menu menu = pmenu.getMenu();
+        for (int i = 0; i < strId.length; i++) {
+            menu.add(Menu.NONE, Menu.FIRST + i, 0, mContext.getString(strId[i]));
+        }
+        setMenuIcon(true);
+
+    }
+
+    public Control[] getControls() {
+        return null;
+    }
+    @Override
+    public void onStartTrackingTouch(SeekBar arg0) {
+
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar arg0) {
+
+    }
+
+    @Override
+    public void swapLeft(MenuItem item) {
+
+    }
+
+    @Override
+    public void swapRight(MenuItem item) {
+
+    }
+
+    public void detach() {
+        if (mImageShow != null) {
+            mImageShow.unselect();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCrop.java b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
new file mode 100644
index 0000000..24a83cd
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCrop.java
@@ -0,0 +1,133 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.imageshow.ImageCrop;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorCrop extends Editor implements EditorInfo {
+    public static final int ID = R.id.editorCrop;
+    private static final String LOGTAG = "EditorCrop";
+
+    ImageCrop mImageCrop;
+    private String mAspectString = "";
+    private boolean mCropActionFlag = false;
+    private CropExtras mCropExtras = null;
+
+    public EditorCrop() {
+        super(ID);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        if (mImageCrop == null) {
+            // TODO: need this for now because there's extra state in ImageCrop.
+            // all the state instead should be in the representation.
+            // Same thing for the other geometry editors.
+            mImageCrop = new ImageCrop(context);
+        }
+        mView = mImageShow = mImageCrop;
+        mImageCrop.setImageLoader(MasterImage.getImage().getImageLoader());
+        mImageCrop.setEditor(this);
+        mImageCrop.syncLocalToMasterGeometry();
+        mImageCrop.setCropActionFlag(mCropActionFlag);
+        if (mCropActionFlag) {
+            mImageCrop.setExtras(mCropExtras);
+            mImageCrop.setAspectString(mAspectString);
+            mImageCrop.clear();
+        } else {
+            mImageCrop.setExtras(null);
+        }
+    }
+
+    @Override
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+        view.setText(mContext.getString(R.string.crop));
+        view.setOnClickListener(new OnClickListener() {
+
+                @Override
+            public void onClick(View arg0) {
+                showPopupMenu(accessoryViewList);
+            }
+        });
+    }
+
+    private void showPopupMenu(LinearLayout accessoryViewList) {
+        final Button button = (Button) accessoryViewList.findViewById(
+                R.id.applyEffect);
+        if (button == null) {
+            return;
+        }
+        final PopupMenu popupMenu = new PopupMenu(mImageShow.getActivity(), button);
+        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_crop, popupMenu.getMenu());
+        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+
+            @Override
+            public boolean onMenuItemClick(MenuItem item) {
+                mImageCrop.setAspectButton(item.getItemId());
+                return true;
+            }
+        });
+        popupMenu.show();
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.crop;
+    }
+
+    @Override
+    public int getOverlayId() {
+        return R.drawable.filtershow_button_geometry_crop;
+    }
+
+    @Override
+    public boolean getOverlayOnly() {
+        return true;
+    }
+
+    public void setExtras(CropExtras cropExtras) {
+        mCropExtras = cropExtras;
+    }
+
+    public void setAspectString(String s) {
+        mAspectString = s;
+    }
+
+    public void setCropActionFlag(boolean b) {
+        mCropActionFlag = b;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorCurves.java b/src/com/android/gallery3d/filtershow/editors/EditorCurves.java
new file mode 100644
index 0000000..d84d9fc
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorCurves.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.ui.ImageCurves;
+
+public class EditorCurves extends Editor {
+    public static final int ID = R.id.imageCurves;
+    ImageCurves mImageCurves;
+
+    public EditorCurves() {
+        super(ID);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mView = mImageShow = mImageCurves = new ImageCurves(context);
+        mImageCurves.setEditor(this);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        FilterRepresentation rep = getLocalRepresentation();
+        if (rep != null && getLocalRepresentation() instanceof FilterCurvesRepresentation) {
+            FilterCurvesRepresentation drawRep = (FilterCurvesRepresentation) rep;
+            mImageCurves.setFilterDrawRepresentation(drawRep);
+        }
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorDraw.java b/src/com/android/gallery3d/filtershow/editors/EditorDraw.java
new file mode 100644
index 0000000..4b09051
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorDraw.java
@@ -0,0 +1,158 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.colorpicker.ColorGridDialog;
+import com.android.gallery3d.filtershow.colorpicker.RGBListener;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterDraw;
+import com.android.gallery3d.filtershow.imageshow.ImageDraw;
+
+public class EditorDraw extends Editor {
+    private static final String LOGTAG = "EditorDraw";
+    public static final int ID = R.id.editorDraw;
+    public ImageDraw mImageDraw;
+
+    public EditorDraw() {
+        super(ID);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mView = mImageShow = mImageDraw = new ImageDraw(context);
+        mImageDraw.setEditor(this);
+
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        FilterRepresentation rep = getLocalRepresentation();
+
+        if (rep != null && getLocalRepresentation() instanceof FilterDrawRepresentation) {
+            FilterDrawRepresentation drawRep = (FilterDrawRepresentation) getLocalRepresentation();
+            mImageDraw.setFilterDrawRepresentation(drawRep);
+        }
+    }
+
+    @Override
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+        view.setText(mContext.getString(R.string.draw_style));
+        view.setOnClickListener(new OnClickListener() {
+
+                @Override
+            public void onClick(View arg0) {
+                showPopupMenu(accessoryViewList);
+            }
+        });
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+
+    private void showPopupMenu(LinearLayout accessoryViewList) {
+        final Button button = (Button) accessoryViewList.findViewById(
+                R.id.applyEffect);
+        if (button == null) {
+            return;
+        }
+        final PopupMenu popupMenu = new PopupMenu(mImageShow.getActivity(), button);
+        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_draw, popupMenu.getMenu());
+        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+
+            @Override
+            public boolean onMenuItemClick(MenuItem item) {
+                ImageFilterDraw filter = (ImageFilterDraw) mImageShow.getCurrentFilter();
+                if (item.getItemId() == R.id.draw_menu_color) {
+                    showColorGrid(item);
+                } else if (item.getItemId() == R.id.draw_menu_size) {
+                    showSizeDialog(item);
+                } else if (item.getItemId() == R.id.draw_menu_style_brush_marker) {
+                    ImageDraw idraw = (ImageDraw) mImageShow;
+                    idraw.setStyle(ImageFilterDraw.BRUSH_STYLE_MARKER);
+                } else if (item.getItemId() == R.id.draw_menu_style_brush_spatter) {
+                    ImageDraw idraw = (ImageDraw) mImageShow;
+                    idraw.setStyle(ImageFilterDraw.BRUSH_STYLE_SPATTER);
+                } else if (item.getItemId() == R.id.draw_menu_style_line) {
+                    ImageDraw idraw = (ImageDraw) mImageShow;
+                    idraw.setStyle(ImageFilterDraw.SIMPLE_STYLE);
+                } else if (item.getItemId() == R.id.draw_menu_clear) {
+                    ImageDraw idraw = (ImageDraw) mImageShow;
+                    idraw.resetParameter();
+                    commitLocalRepresentation();
+                }
+                mView.invalidate();
+                return true;
+            }
+        });
+        popupMenu.show();
+    }
+
+    public void showSizeDialog(final MenuItem item) {
+        FilterShowActivity ctx = mImageShow.getActivity();
+        final Dialog dialog = new Dialog(ctx);
+        dialog.setTitle(R.string.draw_size_title);
+        dialog.setContentView(R.layout.filtershow_draw_size);
+        final SeekBar bar = (SeekBar) dialog.findViewById(R.id.sizeSeekBar);
+        ImageDraw idraw = (ImageDraw) mImageShow;
+        bar.setProgress(idraw.getSize());
+        Button button = (Button) dialog.findViewById(R.id.sizeAcceptButton);
+        button.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(View arg0) {
+                int p = bar.getProgress();
+                ImageDraw idraw = (ImageDraw) mImageShow;
+                idraw.setSize(p + 1);
+                dialog.dismiss();
+            }
+        });
+        dialog.show();
+    }
+
+    public void showColorGrid(final MenuItem item) {
+        RGBListener cl = new RGBListener() {
+            @Override
+            public void setColor(int rgb) {
+                ImageDraw idraw = (ImageDraw) mImageShow;
+                idraw.setColor(rgb);
+            }
+        };
+        ColorGridDialog cpd = new ColorGridDialog(mImageShow.getActivity(), cl);
+        cpd.show();
+        LayoutParams params = cpd.getWindow().getAttributes();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorFlip.java b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
new file mode 100644
index 0000000..de6240c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorFlip.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageFlip;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorFlip extends Editor implements EditorInfo {
+    public static final String LOGTAG = "EditorFlip";
+    public static final int ID = R.id.editorFlip;
+    ImageFlip mImageFlip;
+
+    public EditorFlip() {
+        super(ID);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        if (mImageFlip == null) {
+            mImageFlip = new ImageFlip(context);
+        }
+        mView = mImageShow = mImageFlip;
+        mImageFlip.setImageLoader(MasterImage.getImage().getImageLoader());
+        mImageFlip.setEditor(this);
+        mImageFlip.syncLocalToMasterGeometry();
+    }
+
+    @Override
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                mImageFlip.flip();
+                mImageFlip.saveAndSetPreset();
+            }
+        });
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.mirror;
+    }
+
+    @Override
+    public int getOverlayId() {
+        return R.drawable.filtershow_button_geometry_flip;
+    }
+
+    @Override
+    public boolean getOverlayOnly() {
+        return true;
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+
+    @Override
+    public boolean showsPopupIndicator() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorInfo.java b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
new file mode 100644
index 0000000..75afe49
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorInfo.java
@@ -0,0 +1,23 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+public interface EditorInfo {
+    public int getTextId();
+    public int getOverlayId();
+    public boolean getOverlayOnly();
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorPanel.java b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java
new file mode 100644
index 0000000..e35bc8f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorPanel.java
@@ -0,0 +1,145 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentTransaction;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.category.MainPanel;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.state.StatePanel;
+
+public class EditorPanel extends Fragment {
+
+    private static final String LOGTAG = "EditorPanel";
+
+    private LinearLayout mMainView;
+    private Editor mEditor;
+    private int mEditorID;
+
+    public void setEditor(int editor) {
+        mEditorID = editor;
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        FilterShowActivity filterShowActivity = (FilterShowActivity) activity;
+        mEditor = filterShowActivity.getEditor(mEditorID);
+    }
+
+    public void cancelCurrentFilter() {
+        MasterImage masterImage = MasterImage.getImage();
+        HistoryAdapter adapter = masterImage.getHistory();
+
+        int position = adapter.undo();
+        masterImage.onHistoryItemClick(position);
+        ((FilterShowActivity)getActivity()).invalidateViews();
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        FilterShowActivity activity = (FilterShowActivity) getActivity();
+        if (mMainView != null) {
+            if (mMainView.getParent() != null) {
+                ViewGroup parent = (ViewGroup) mMainView.getParent();
+                parent.removeView(mMainView);
+            }
+            showImageStatePanel(activity.isShowingImageStatePanel());
+            return mMainView;
+        }
+        mMainView = (LinearLayout) inflater.inflate(R.layout.filtershow_editor_panel, null);
+
+        View actionControl = mMainView.findViewById(R.id.panelAccessoryViewList);
+        View editControl = mMainView.findViewById(R.id.controlArea);
+        ImageButton cancelButton = (ImageButton) mMainView.findViewById(R.id.cancelFilter);
+        ImageButton applyButton = (ImageButton) mMainView.findViewById(R.id.applyFilter);
+        Button editTitle = (Button) mMainView.findViewById(R.id.applyEffect);
+        cancelButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                cancelCurrentFilter();
+                FilterShowActivity activity = (FilterShowActivity) getActivity();
+                activity.backToMain();
+            }
+        });
+        applyButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                MasterImage.getImage().invalidateFiltersOnly();
+                FilterShowActivity activity = (FilterShowActivity) getActivity();
+                activity.backToMain();
+            }
+        });
+
+        Button toggleState = (Button) mMainView.findViewById(R.id.toggle_state);
+
+        mEditor = activity.getEditor(mEditorID);
+        if (mEditor != null) {
+            mEditor.setUpEditorUI(actionControl, editControl, editTitle, toggleState);
+            mEditor.getImageShow().select();
+            mEditor.reflectCurrentFilter();
+            if (mEditor.useUtilityPanel()) {
+                mEditor.openUtilityPanel((LinearLayout) actionControl);
+            }
+        }
+
+        showImageStatePanel(activity.isShowingImageStatePanel());
+        return mMainView;
+    }
+
+    @Override
+    public void onDetach() {
+        if (mEditor != null) {
+            mEditor.detach();
+        }
+        super.onDetach();
+    }
+
+    public void showImageStatePanel(boolean show) {
+        if (mMainView.findViewById(R.id.state_panel_container) == null) {
+            return;
+        }
+        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
+        Fragment panel = getActivity().getSupportFragmentManager().findFragmentByTag(
+                MainPanel.FRAGMENT_TAG);
+        if (panel == null || panel instanceof MainPanel) {
+            transaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
+        }
+        if (show) {
+            StatePanel statePanel = new StatePanel();
+            transaction.replace(R.id.state_panel_container, statePanel, StatePanel.FRAGMENT_TAG);
+        } else {
+            Fragment statePanel = getChildFragmentManager().findFragmentByTag(StatePanel.FRAGMENT_TAG);
+            if (statePanel != null) {
+                transaction.remove(statePanel);
+            }
+        }
+        transaction.commit();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
new file mode 100644
index 0000000..b0e88dd
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRedEye.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRedEyeRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageRedEye;
+
+/**
+ * The editor with no slider for filters without UI
+ */
+public class EditorRedEye extends Editor {
+    public static int ID = R.id.editorRedEye;
+    private final String LOGTAG = "EditorRedEye";
+    ImageRedEye mImageRedEyes;
+
+    public EditorRedEye() {
+        super(ID);
+    }
+
+    protected EditorRedEye(int id) {
+        super(id);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mView = mImageShow = mImageRedEyes=  new ImageRedEye(context);
+        mImageRedEyes.setEditor(this);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        FilterRepresentation rep = getLocalRepresentation();
+        if (rep != null && getLocalRepresentation() instanceof FilterRedEyeRepresentation) {
+            FilterRedEyeRepresentation redEyeRep = (FilterRedEyeRepresentation) rep;
+
+            mImageRedEyes.setRepresentation(redEyeRep);
+        }
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorRotate.java b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
new file mode 100644
index 0000000..a637c08
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorRotate.java
@@ -0,0 +1,88 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageRotate;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorRotate extends Editor implements EditorInfo {
+    public static final String LOGTAG = "EditorRotate";
+    public static final int ID = R.id.editorRotate;
+    ImageRotate mImageRotate;
+
+    public EditorRotate() {
+        super(ID);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        if (mImageRotate == null) {
+            mImageRotate = new ImageRotate(context);
+        }
+        mView = mImageShow = mImageRotate;
+        mImageRotate.setImageLoader(MasterImage.getImage().getImageLoader());
+        mImageRotate.setEditor(this);
+        mImageRotate.syncLocalToMasterGeometry();
+    }
+
+    @Override
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        final Button button = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                mImageRotate.rotate();
+                button.setText(mContext.getString(getTextId()) + " " + mImageRotate.getLocalValue());
+                mImageRotate.saveAndSetPreset();
+            }
+        });
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.rotate;
+    }
+
+    @Override
+    public int getOverlayId() {
+        return R.drawable.filtershow_button_geometry_rotate;
+    }
+
+    @Override
+    public boolean getOverlayOnly() {
+        return true;
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+
+    @Override
+    public boolean showsPopupIndicator() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
new file mode 100644
index 0000000..dbc6ca0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorStraighten.java
@@ -0,0 +1,82 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
+import com.android.gallery3d.filtershow.imageshow.ImageStraighten;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class EditorStraighten extends Editor implements EditorInfo {
+    public static final int ID = R.id.editorStraighten;
+    ImageStraighten mImageStraighten;
+    GeometryMetadata mGeometryMetadata;
+
+    public EditorStraighten() {
+        super(ID);
+        mShowParameter = SHOW_VALUE_INT;
+    }
+
+    // TODO use filter reflection like
+    @Override
+    public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+        String apply = context.getString(R.string.apply_effect);
+        apply += " " + effectName;
+        return apply.toUpperCase();
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        if (mImageStraighten == null) {
+            mImageStraighten = new ImageStraighten(context);
+        }
+        mView = mImageShow = mImageStraighten;
+        mImageStraighten.setImageLoader(MasterImage.getImage().getImageLoader());
+        mImageStraighten.setEditor(this);
+        mImageStraighten.syncLocalToMasterGeometry();
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.straighten;
+    }
+
+    @Override
+    public int getOverlayId() {
+        return R.drawable.filtershow_button_geometry_straighten;
+    }
+
+    @Override
+    public boolean getOverlayOnly() {
+        return true;
+    }
+
+    @Override
+    public boolean showsSeekBar() {
+        return false;
+    }
+
+    @Override
+    public boolean showsPopupIndicator() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
new file mode 100644
index 0000000..9376fbe
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorTinyPlanet.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet;
+
+public class EditorTinyPlanet extends BasicEditor {
+    public static final int ID = R.id.tinyPlanetEditor;
+    private static final String LOGTAG = "EditorTinyPlanet";
+    ImageTinyPlanet mImageTinyPlanet;
+
+    public EditorTinyPlanet() {
+        super(ID, R.layout.filtershow_tiny_planet_editor, R.id.imageTinyPlanet);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mImageTinyPlanet = (ImageTinyPlanet) mImageShow;
+        mImageTinyPlanet.setEditor(this);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        FilterRepresentation rep = getLocalRepresentation();
+        if (rep != null && rep instanceof FilterTinyPlanetRepresentation) {
+            FilterTinyPlanetRepresentation drawRep = (FilterTinyPlanetRepresentation) rep;
+            mImageTinyPlanet.setRepresentation(drawRep);
+        }
+    }
+
+    public void updateUI() {
+        if (mControl != null) {
+            mControl.updateUI();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorVignette.java b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
new file mode 100644
index 0000000..7127b21
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorVignette.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterVignetteRepresentation;
+import com.android.gallery3d.filtershow.imageshow.ImageVignette;
+
+public class EditorVignette extends ParametricEditor {
+    public static final int ID = R.id.vignetteEditor;
+    private static final String LOGTAG = "EditorVignettePlanet";
+    ImageVignette mImageVignette;
+
+    public EditorVignette() {
+        super(ID, R.layout.filtershow_vignette_editor, R.id.imageVignette);
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mImageVignette = (ImageVignette) mImageShow;
+        mImageVignette.setEditor(this);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+
+        FilterRepresentation rep = getLocalRepresentation();
+        if (rep != null && getLocalRepresentation() instanceof FilterVignetteRepresentation) {
+            FilterVignetteRepresentation drawRep = (FilterVignetteRepresentation) rep;
+            mImageVignette.setRepresentation(drawRep);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/EditorZoom.java b/src/com/android/gallery3d/filtershow/editors/EditorZoom.java
new file mode 100644
index 0000000..ea8e3d1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/EditorZoom.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import com.android.gallery3d.R;
+
+public class EditorZoom extends BasicEditor {
+    public static final int ID = R.id.imageZoom;
+
+    public EditorZoom() {
+        super(ID, R.layout.filtershow_zoom_editor,R.id.imageZoom);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java
new file mode 100644
index 0000000..d4e66ed
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/ImageOnlyEditor.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.widget.FrameLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
+
+/**
+ * The editor with no slider for filters without UI
+ */
+public class ImageOnlyEditor extends Editor {
+    public final static int ID = R.id.imageOnlyEditor;
+    private final String LOGTAG = "ImageOnlyEditor";
+
+    public ImageOnlyEditor() {
+        super(ID);
+    }
+
+    protected ImageOnlyEditor(int id) {
+        super(id);
+    }
+
+    public boolean useUtilityPanel() {
+        return false;
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        mView = mImageShow = new ImageShow(context);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
new file mode 100644
index 0000000..9ec858c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
@@ -0,0 +1,206 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.ActionSlider;
+import com.android.gallery3d.filtershow.controller.BasicSlider;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterActionAndInt;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+import com.android.gallery3d.filtershow.controller.ParameterStyles;
+import com.android.gallery3d.filtershow.controller.StyleChooser;
+import com.android.gallery3d.filtershow.controller.TitledSlider;
+import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+public class ParametricEditor extends Editor {
+    private int mLayoutID;
+    private int mViewID;
+    public static int ID = R.id.editorParametric;
+    private final String LOGTAG = "ParametricEditor";
+    protected Control mControl;
+    public static final int MINIMUM_WIDTH = 600;
+    public static final int MINIMUM_HEIGHT = 800;
+    View mActionButton;
+    View mEditControl;
+    static HashMap<String, Class> portraitMap = new HashMap<String, Class>();
+    static HashMap<String, Class> landscapeMap = new HashMap<String, Class>();
+    static {
+        portraitMap.put(ParameterInteger.sParameterType, BasicSlider.class);
+        landscapeMap.put(ParameterInteger.sParameterType, TitledSlider.class);
+        portraitMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
+        landscapeMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
+        portraitMap.put(ParameterStyles.sParameterType, StyleChooser.class);
+        landscapeMap.put(ParameterStyles.sParameterType, StyleChooser.class);
+    }
+
+    static Constructor getConstructor(Class cl) {
+        try {
+            return cl.getConstructor(Context.class, ViewGroup.class);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public ParametricEditor() {
+        super(ID);
+    }
+
+    protected ParametricEditor(int id) {
+        super(id);
+    }
+
+    protected ParametricEditor(int id, int layoutID, int viewID) {
+        super(id);
+        mLayoutID = layoutID;
+        mViewID = viewID;
+    }
+
+    @Override
+    public String calculateUserMessage(Context context, String effectName, Object parameterValue) {
+        String apply = "";
+
+        if (mShowParameter == SHOW_VALUE_INT & useCompact(context)) {
+           if (getLocalRepresentation() instanceof FilterBasicRepresentation) {
+            FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+                apply += " " + effectName.toUpperCase() + " " + interval.getStateRepresentation();
+           } else {
+                apply += " " + effectName.toUpperCase() + " " + parameterValue;
+           }
+        } else {
+            apply += " " + effectName.toUpperCase();
+        }
+        return apply;
+    }
+
+    @Override
+    public void createEditor(Context context, FrameLayout frameLayout) {
+        super.createEditor(context, frameLayout);
+        unpack(mViewID, mLayoutID);
+    }
+
+    @Override
+    public void reflectCurrentFilter() {
+        super.reflectCurrentFilter();
+        if (getLocalRepresentation() != null
+                && getLocalRepresentation() instanceof FilterBasicRepresentation) {
+            FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
+            mControl.setPrameter(interval);
+        }
+    }
+
+    @Override
+    public Control[] getControls() {
+        BasicSlider slider = new BasicSlider();
+        return new Control[] {
+                slider
+        };
+    }
+
+    // TODO: need a better way to decide which representation
+    static boolean useCompact(Context context) {
+        WindowManager w = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE));
+        Point size = new Point();
+        w.getDefaultDisplay().getSize(size);
+        if (size.x < size.y) { // if tall than wider
+            return true;
+        }
+        if (size.x < MINIMUM_WIDTH) {
+            return true;
+        }
+        if (size.y < MINIMUM_HEIGHT) {
+            return true;
+        }
+        return false;
+    }
+
+    protected Parameter getParameterToEdit(FilterRepresentation rep) {
+        if (this instanceof Parameter) {
+            return (Parameter) this;
+        } else if (rep instanceof Parameter) {
+            return ((Parameter) rep);
+        }
+        return null;
+    }
+
+    @Override
+    public void setUtilityPanelUI(View actionButton, View editControl) {
+        mActionButton = actionButton;
+        mEditControl = editControl;
+        FilterRepresentation rep = getLocalRepresentation();
+        Parameter param = getParameterToEdit(rep);
+        if (param != null) {
+            control(param, editControl);
+        } else {
+            mSeekBar = new SeekBar(editControl.getContext());
+            LayoutParams lp = new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+            mSeekBar.setLayoutParams(lp);
+            ((LinearLayout) editControl).addView(mSeekBar);
+            mSeekBar.setOnSeekBarChangeListener(this);
+        }
+    }
+
+    protected void control(Parameter p, View editControl) {
+        String pType = p.getParameterType();
+        Context context = editControl.getContext();
+        Class c = ((useCompact(context)) ? portraitMap : landscapeMap).get(pType);
+
+        if (c != null) {
+            try {
+                mControl = (Control) c.newInstance();
+                p.setController(mControl);
+                mControl.setUp((ViewGroup) editControl, p, this);
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Error in loading Control ", e);
+            }
+        } else {
+            Log.e(LOGTAG, "Unable to find class for " + pType);
+            for (String string : portraitMap.keySet()) {
+                Log.e(LOGTAG, "for " + string + " use " + portraitMap.get(string));
+            }
+        }
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar arg0) {
+    }
+
+    @Override
+    public void onStopTrackingTouch(SeekBar arg0) {
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/editors/SwapButton.java b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
new file mode 100644
index 0000000..bb4432e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/editors/SwapButton.java
@@ -0,0 +1,111 @@
+/*
+ * 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.gallery3d.filtershow.editors;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+public class SwapButton extends Button implements GestureDetector.OnGestureListener {
+
+    public static int ANIM_DURATION = 200;
+
+    public interface SwapButtonListener {
+        public void swapLeft(MenuItem item);
+        public void swapRight(MenuItem item);
+    }
+
+    private GestureDetector mDetector;
+    private SwapButtonListener mListener;
+    private Menu mMenu;
+    private int mCurrentMenuIndex;
+
+    public SwapButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mDetector = new GestureDetector(context, this);
+    }
+
+    public SwapButtonListener getListener() {
+        return mListener;
+    }
+
+    public void setListener(SwapButtonListener listener) {
+        mListener = listener;
+    }
+
+    public boolean onTouchEvent(MotionEvent me) {
+        if (!mDetector.onTouchEvent(me)) {
+            return super.onTouchEvent(me);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return true;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        callOnClick();
+        return true;
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent e) {
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        if (mMenu == null) {
+            return false;
+        }
+        if (e1.getX() - e2.getX() > 0) {
+            // right to left
+            mCurrentMenuIndex++;
+            if (mCurrentMenuIndex == mMenu.size()) {
+                mCurrentMenuIndex = 0;
+            }
+            if (mListener != null) {
+                mListener.swapRight(mMenu.getItem(mCurrentMenuIndex));
+            }
+        } else {
+            // left to right
+            mCurrentMenuIndex--;
+            if (mCurrentMenuIndex < 0) {
+                mCurrentMenuIndex = mMenu.size() - 1;
+            }
+            if (mListener != null) {
+                mListener.swapLeft(mMenu.getItem(mCurrentMenuIndex));
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
new file mode 100644
index 0000000..9927a0a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -0,0 +1,181 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public abstract class BaseFiltersManager {
+    protected HashMap<Class, ImageFilter> mFilters = null;
+
+    protected void init() {
+        mFilters = new HashMap<Class, ImageFilter>();
+        Vector<Class> filters = new Vector<Class>();
+        addFilterClasses(filters);
+        for (Class filterClass : filters) {
+            try {
+                Object filterInstance = filterClass.newInstance();
+                if (filterInstance instanceof ImageFilter) {
+                    mFilters.put(filterClass, (ImageFilter) filterInstance);
+                }
+            } catch (InstantiationException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    public ImageFilter getFilter(Class c) {
+        return mFilters.get(c);
+    }
+
+    public ImageFilter getFilterForRepresentation(FilterRepresentation representation) {
+        return mFilters.get(representation.getFilterClass());
+    }
+
+    public void addFilter(Class filterClass, ImageFilter filter) {
+        mFilters.put(filterClass, filter);
+    }
+
+    public FilterRepresentation getRepresentation(Class c) {
+        ImageFilter filter = mFilters.get(c);
+        if (filter != null) {
+            return filter.getDefaultRepresentation();
+        }
+        return null;
+    }
+
+    public void freeFilterResources(ImagePreset preset) {
+        if (preset == null) {
+            return;
+        }
+        Vector<ImageFilter> usedFilters = preset.getUsedFilters(this);
+        for (Class c : mFilters.keySet()) {
+            ImageFilter filter = mFilters.get(c);
+            if (!usedFilters.contains(filter)) {
+                filter.freeResources();
+            }
+        }
+    }
+
+    public void freeRSFilterScripts() {
+        for (Class c : mFilters.keySet()) {
+            ImageFilter filter = mFilters.get(c);
+            if (filter != null && filter instanceof ImageFilterRS) {
+                ((ImageFilterRS) filter).resetScripts();
+            }
+        }
+    }
+
+    protected void addFilterClasses(Vector<Class> filters) {
+        filters.add(ImageFilterTinyPlanet.class);
+        //filters.add(ImageFilterRedEye.class);
+        filters.add(ImageFilterWBalance.class);
+        filters.add(ImageFilterExposure.class);
+        filters.add(ImageFilterVignette.class);
+        filters.add(ImageFilterContrast.class);
+        filters.add(ImageFilterShadows.class);
+        filters.add(ImageFilterHighlights.class);
+        filters.add(ImageFilterVibrance.class);
+        filters.add(ImageFilterSharpen.class);
+        filters.add(ImageFilterCurves.class);
+        // filters.add(ImageFilterDraw.class);
+        filters.add(ImageFilterHue.class);
+        filters.add(ImageFilterSaturated.class);
+        filters.add(ImageFilterBwFilter.class);
+        filters.add(ImageFilterNegative.class);
+        filters.add(ImageFilterEdge.class);
+        filters.add(ImageFilterKMeans.class);
+        filters.add(ImageFilterFx.class);
+        filters.add(ImageFilterBorder.class);
+        filters.add(ImageFilterParametricBorder.class);
+        filters.add(ImageFilterGeometry.class);
+    }
+
+    public void addBorders(Context context, Vector<FilterRepresentation> representations) {
+
+    }
+
+    public void addLooks(Context context, Vector<FilterRepresentation> representations) {
+        int[] drawid = {
+                R.drawable.filtershow_fx_0005_punch,
+                R.drawable.filtershow_fx_0000_vintage,
+                R.drawable.filtershow_fx_0004_bw_contrast,
+                R.drawable.filtershow_fx_0002_bleach,
+                R.drawable.filtershow_fx_0001_instant,
+                R.drawable.filtershow_fx_0007_washout,
+                R.drawable.filtershow_fx_0003_blue_crush,
+                R.drawable.filtershow_fx_0008_washout_color,
+                R.drawable.filtershow_fx_0006_x_process
+        };
+
+        int[] fxNameid = {
+                R.string.ffx_punch,
+                R.string.ffx_vintage,
+                R.string.ffx_bw_contrast,
+                R.string.ffx_bleach,
+                R.string.ffx_instant,
+                R.string.ffx_washout,
+                R.string.ffx_blue_crush,
+                R.string.ffx_washout_color,
+                R.string.ffx_x_process
+        };
+
+        for (int i = 0; i < drawid.length; i++) {
+            FilterFxRepresentation fx = new FilterFxRepresentation(
+                    context.getString(fxNameid[i]), drawid[i], fxNameid[i]);
+            representations.add(fx);
+        }
+    }
+
+    public void addEffects(Vector<FilterRepresentation> representations) {
+        representations.add(getRepresentation(ImageFilterTinyPlanet.class));
+        representations.add(getRepresentation(ImageFilterWBalance.class));
+        representations.add(getRepresentation(ImageFilterExposure.class));
+        representations.add(getRepresentation(ImageFilterVignette.class));
+        representations.add(getRepresentation(ImageFilterContrast.class));
+        representations.add(getRepresentation(ImageFilterShadows.class));
+        representations.add(getRepresentation(ImageFilterHighlights.class));
+        representations.add(getRepresentation(ImageFilterVibrance.class));
+        representations.add(getRepresentation(ImageFilterSharpen.class));
+        representations.add(getRepresentation(ImageFilterCurves.class));
+        representations.add(getRepresentation(ImageFilterHue.class));
+        representations.add(getRepresentation(ImageFilterSaturated.class));
+        representations.add(getRepresentation(ImageFilterBwFilter.class));
+        representations.add(getRepresentation(ImageFilterNegative.class));
+        representations.add(getRepresentation(ImageFilterEdge.class));
+        representations.add(getRepresentation(ImageFilterKMeans.class));
+    }
+
+    public void addTools(Vector<FilterRepresentation> representations) {
+        //representations.add(getRepresentation(ImageFilterRedEye.class));
+        // representations.add(getRepresentation(ImageFilterDraw.class));
+    }
+
+    public void setFilterResources(Resources resources) {
+        ImageFilterBorder filterBorder = (ImageFilterBorder) getFilter(ImageFilterBorder.class);
+        filterBorder.setResources(resources);
+        ImageFilterFx filterFx = (ImageFilterFx) getFilter(ImageFilterFx.class);
+        filterFx.setResources(resources);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
new file mode 100644
index 0000000..4d0651e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
@@ -0,0 +1,174 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.FilterView;
+import com.android.gallery3d.filtershow.controller.Parameter;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
+
+public class FilterBasicRepresentation extends FilterRepresentation implements ParameterInteger {
+    private static final String LOGTAG = "FilterBasicRep";
+    private int mMinimum;
+    private int mValue;
+    private int mMaximum;
+    private int mDefaultValue;
+    private int mPreviewValue;
+    private boolean mLogVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+
+    public FilterBasicRepresentation(String name, int minimum, int value, int maximum) {
+        super(name);
+        mMinimum = minimum;
+        mMaximum = maximum;
+        setValue(value);
+    }
+
+    @Override
+    public String toString() {
+        return getName() + " : " + mMinimum + " < " + mValue + " < " + mMaximum;
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterBasicRepresentation representation = (FilterBasicRepresentation) super.clone();
+        representation.setMinimum(getMinimum());
+        representation.setMaximum(getMaximum());
+        representation.setValue(getValue());
+        if (mLogVerbose) {
+            Log.v(LOGTAG, "cloning from <" + this + "> to <" + representation + ">");
+        }
+        return representation;
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterBasicRepresentation) {
+            FilterBasicRepresentation representation = (FilterBasicRepresentation) a;
+            setMinimum(representation.getMinimum());
+            setMaximum(representation.getMaximum());
+            setValue(representation.getValue());
+            setDefaultValue(representation.getDefaultValue());
+            setPreviewValue(representation.getPreviewValue());
+        }
+    }
+
+    @Override
+    public boolean equals(FilterRepresentation representation) {
+        if (!super.equals(representation)) {
+            return false;
+        }
+        if (representation instanceof FilterBasicRepresentation) {
+            FilterBasicRepresentation basic = (FilterBasicRepresentation) representation;
+            if (basic.mMinimum == mMinimum
+                    && basic.mMaximum == mMaximum
+                    && basic.mValue == mValue
+                    && basic.mDefaultValue == mDefaultValue
+                    && basic.mPreviewValue == mPreviewValue) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int getMinimum() {
+        return mMinimum;
+    }
+
+    public void setMinimum(int minimum) {
+        mMinimum = minimum;
+    }
+
+    @Override
+    public int getValue() {
+        return mValue;
+    }
+
+    @Override
+    public void setValue(int value) {
+        mValue = value;
+        if (mValue < mMinimum) {
+            mValue = mMinimum;
+        }
+        if (mValue > mMaximum) {
+            mValue = mMaximum;
+        }
+    }
+
+    @Override
+    public int getMaximum() {
+        return mMaximum;
+    }
+
+    public void setMaximum(int maximum) {
+        mMaximum = maximum;
+    }
+
+    public void setDefaultValue(int defaultValue) {
+        mDefaultValue = defaultValue;
+    }
+
+    @Override
+    public int getDefaultValue() {
+        return mDefaultValue;
+    }
+
+    public int getPreviewValue() {
+        return mPreviewValue;
+    }
+
+    public void setPreviewValue(int previewValue) {
+        mPreviewValue = previewValue;
+    }
+
+    @Override
+    public String getStateRepresentation() {
+        int val = getValue();
+        return ((val > 0) ? "+" : "") + val;
+    }
+
+    @Override
+    public String getParameterType(){
+        return sParameterType;
+    }
+
+    @Override
+    public void setController(Control control) {
+    }
+
+    @Override
+    public String getValueString() {
+        return getStateRepresentation();
+    }
+
+    @Override
+    public String getParameterName() {
+        return getName();
+    }
+
+    @Override
+    public void setFilterView(FilterView editor) {
+    }
+
+    @Override
+    public void copyFrom(Parameter src) {
+        useParametersFrom((FilterBasicRepresentation) src);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java
new file mode 100644
index 0000000..b2664a3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterColorBorderRepresentation.java
@@ -0,0 +1,113 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterColorBorderRepresentation extends FilterRepresentation {
+    private int mColor;
+    private int mBorderSize;
+    private int mBorderRadius;
+
+    public FilterColorBorderRepresentation(int color, int size, int radius) {
+        super("ColorBorder");
+        mColor = color;
+        mBorderSize = size;
+        mBorderRadius = radius;
+        setFilterClass(ImageFilterParametricBorder.class);
+        setPriority(FilterRepresentation.TYPE_BORDER);
+        setTextId(R.string.borders);
+        setEditorId(ImageOnlyEditor.ID);
+        setShowEditingControls(false);
+        setShowParameterValue(false);
+        setShowUtilityPanel(false);
+    }
+
+    public String toString() {
+        return "FilterBorder: " + getName();
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterColorBorderRepresentation representation = (FilterColorBorderRepresentation) super.clone();
+        representation.setName(getName());
+        representation.setColor(getColor());
+        representation.setBorderSize(getBorderSize());
+        representation.setBorderRadius(getBorderRadius());
+        return representation;
+    }
+
+    public void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterColorBorderRepresentation) {
+            FilterColorBorderRepresentation representation = (FilterColorBorderRepresentation) a;
+            setName(representation.getName());
+            setColor(representation.getColor());
+            setBorderSize(representation.getBorderSize());
+            setBorderRadius(representation.getBorderRadius());
+        }
+    }
+
+    @Override
+    public boolean equals(FilterRepresentation representation) {
+        if (!super.equals(representation)) {
+            return false;
+        }
+        if (representation instanceof FilterColorBorderRepresentation) {
+            FilterColorBorderRepresentation border = (FilterColorBorderRepresentation) representation;
+            if (border.mColor == mColor
+                    && border.mBorderSize == mBorderSize
+                    && border.mBorderRadius == mBorderRadius) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean allowsMultipleInstances() {
+        return true;
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.borders;
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+
+    public void setColor(int color) {
+        mColor = color;
+    }
+
+    public int getBorderSize() {
+        return mBorderSize;
+    }
+
+    public void setBorderSize(int borderSize) {
+        mBorderSize = borderSize;
+    }
+
+    public int getBorderRadius() {
+        return mBorderRadius;
+    }
+
+    public void setBorderRadius(int borderRadius) {
+        mBorderRadius = borderRadius;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
new file mode 100644
index 0000000..cbcae4b
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
@@ -0,0 +1,82 @@
+package com.android.gallery3d.filtershow.filters;
+
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.ui.Spline;
+
+/**
+ * TODO: Insert description here. (generated by hoford)
+ */
+public class FilterCurvesRepresentation extends FilterRepresentation {
+    private static final String LOGTAG = "FilterCurvesRepresentation";
+
+    private Spline[] mSplines = new Spline[4];
+
+    public FilterCurvesRepresentation() {
+        super("Curves");
+        setFilterClass(ImageFilterCurves.class);
+        setTextId(R.string.curvesRGB);
+        setButtonId(R.id.curvesButtonRGB);
+        setOverlayId(R.drawable.filtershow_button_colors_curve);
+        setEditorId(R.id.imageCurves);
+        setShowEditingControls(false);
+        setShowParameterValue(false);
+        setShowUtilityPanel(true);
+        setSupportsPartialRendering(true);
+        reset();
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterCurvesRepresentation rep = new FilterCurvesRepresentation();
+        rep.useParametersFrom(this);
+        return rep;
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (!(a instanceof FilterCurvesRepresentation)) {
+            Log.v(LOGTAG, "cannot use parameters from " + a);
+            return;
+        }
+        FilterCurvesRepresentation representation = (FilterCurvesRepresentation) a;
+        Spline[] spline = new Spline[4];
+        for (int i = 0; i < spline.length; i++) {
+            Spline sp = representation.mSplines[i];
+            if (sp != null) {
+                spline[i] = new Spline(sp);
+            } else {
+                spline[i] = new Spline();
+            }
+        }
+        mSplines = spline;
+    }
+
+    public boolean isNil() {
+        for (int i = 0; i < 4; i++) {
+            if (getSpline(i) != null && !getSpline(i).isOriginal()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void reset() {
+        Spline spline = new Spline();
+
+        spline.addPoint(0.0f, 1.0f);
+        spline.addPoint(1.0f, 0.0f);
+
+        for (int i = 0; i < 4; i++) {
+            mSplines[i] = new Spline(spline);
+        }
+    }
+
+    public void setSpline(int splineIndex, Spline s) {
+        mSplines[splineIndex] = s;
+    }
+    public Spline getSpline(int splineIndex) {
+        return mSplines[splineIndex];
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java
new file mode 100644
index 0000000..3807ee1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDirectRepresentation.java
@@ -0,0 +1,29 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+public class FilterDirectRepresentation extends FilterRepresentation {
+
+    public FilterDirectRepresentation(String name) {
+        super(name);
+    }
+
+    public boolean isNil() {
+        return true;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
new file mode 100644
index 0000000..dc59b0c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
@@ -0,0 +1,164 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.graphics.Path;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorDraw;
+
+import java.util.Vector;
+
+public class FilterDrawRepresentation extends FilterRepresentation {
+    private static final String LOGTAG = "FilterDrawRepresentation";
+
+    public static class StrokeData implements Cloneable {
+        public byte mType;
+        public Path mPath;
+        public float mRadius;
+        public int mColor;
+        public int noPoints = 0;
+        @Override
+        public String toString() {
+            return "stroke(" + mType + ", path(" + (mPath) + "), " + mRadius + " , "
+                    + Integer.toHexString(mColor) + ")";
+        }
+        @Override
+        public StrokeData clone() throws CloneNotSupportedException {
+            return (StrokeData) super.clone();
+        }
+    }
+
+    private Vector<StrokeData> mDrawing = new Vector<StrokeData>();
+    private StrokeData mCurrent; // used in the currently drawing style
+
+    public FilterDrawRepresentation() {
+        super("Draw");
+        setFilterClass(ImageFilterDraw.class);
+        setPriority(FilterRepresentation.TYPE_VIGNETTE);
+        setTextId(R.string.imageDraw);
+        setButtonId(R.id.drawOnImageButton);
+        setEditorId(EditorDraw.ID);
+        setOverlayId(R.drawable.filtershow_drawing);
+        setOverlayOnly(true);
+    }
+
+    @Override
+    public String toString() {
+        return getName() + " : strokes=" + mDrawing.size()
+                + ((mCurrent == null) ? " no current "
+                        : ("draw=" + mCurrent.mType + " " + mCurrent.noPoints));
+    }
+
+    public Vector<StrokeData> getDrawing() {
+        return mDrawing;
+    }
+
+    public StrokeData getCurrentDrawing() {
+        return mCurrent;
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterDrawRepresentation representation = (FilterDrawRepresentation) super.clone();
+        return representation;
+    }
+
+    @Override
+    public boolean isNil() {
+        return getDrawing().isEmpty();
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterDrawRepresentation) {
+            FilterDrawRepresentation representation = (FilterDrawRepresentation) a;
+            try {
+                if (representation.mCurrent != null) {
+                    mCurrent = (StrokeData) representation.mCurrent.clone();
+                } else {
+                    mCurrent = null;
+                }
+                if (representation.mDrawing != null) {
+                    mDrawing = (Vector<StrokeData>) representation.mDrawing.clone();
+                } else {
+                    mDrawing = null;
+                }
+
+            } catch (CloneNotSupportedException e) {
+                e.printStackTrace();
+            }
+        } else {
+            Log.v(LOGTAG, "cannot use parameters from " + a);
+        }
+    }
+
+    @Override
+    public boolean equals(FilterRepresentation representation) {
+        if (!super.equals(representation)) {
+            return false;
+        }
+        if (representation instanceof FilterDrawRepresentation) {
+            FilterDrawRepresentation fdRep = (FilterDrawRepresentation) representation;
+            if (fdRep.mDrawing.size() != mDrawing.size())
+                return false;
+            if (fdRep.mCurrent == null && mCurrent.mPath == null) {
+                return true;
+            }
+            if (fdRep.mCurrent != null && mCurrent.mPath != null) {
+                if (fdRep.mCurrent.noPoints == mCurrent.noPoints) {
+                    return true;
+                }
+                return false;
+            }
+        }
+        return false;
+    }
+
+    public void startNewSection(byte type, int color, float size, float x, float y) {
+        mCurrent = new StrokeData();
+        mCurrent.mColor = color;
+        mCurrent.mRadius = size;
+        mCurrent.mType = type;
+        mCurrent.mPath = new Path();
+        mCurrent.mPath.moveTo(x, y);
+        mCurrent.noPoints = 0;
+    }
+
+    public void addPoint(float x, float y) {
+        mCurrent.noPoints++;
+        mCurrent.mPath.lineTo(x, y);
+    }
+
+    public void endSection(float x, float y) {
+        mCurrent.mPath.lineTo(x, y);
+        mCurrent.noPoints++;
+        mDrawing.add(mCurrent);
+        mCurrent = null;
+    }
+
+    public void clearCurrentSection() {
+        mCurrent = null;
+    }
+
+    public void clear() {
+        mCurrent = null;
+        mDrawing.clear();
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
new file mode 100644
index 0000000..6e2e7ea
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
@@ -0,0 +1,107 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import com.android.gallery3d.app.Log;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterFxRepresentation extends FilterRepresentation {
+    private static final String LOGTAG = "FilterFxRepresentation";
+    // TODO: When implementing serialization, we should find a unique way of
+    // specifying bitmaps / names (the resource IDs being random)
+    private int mBitmapResource = 0;
+    private int mNameResource = 0;
+
+    public FilterFxRepresentation(String name, int bitmapResource, int nameResource) {
+        super(name);
+        mBitmapResource = bitmapResource;
+        mNameResource = nameResource;
+        setFilterClass(ImageFilterFx.class);
+        setPriority(FilterRepresentation.TYPE_FX);
+        setTextId(nameResource);
+        setEditorId(ImageOnlyEditor.ID);
+        setShowEditingControls(false);
+        setShowParameterValue(false);
+        setShowUtilityPanel(false);
+        setSupportsPartialRendering(true);
+    }
+
+    public String toString() {
+        return "FilterFx: " + hashCode() + " : " + getName() + " bitmap rsc: " + mBitmapResource;
+    }
+
+    @Override
+    public synchronized FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterFxRepresentation representation = (FilterFxRepresentation) super.clone();
+        representation.setName(getName());
+        representation.setBitmapResource(getBitmapResource());
+        representation.setNameResource(getNameResource());
+        return representation;
+    }
+
+    public synchronized void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterFxRepresentation) {
+            FilterFxRepresentation representation = (FilterFxRepresentation) a;
+            setName(representation.getName());
+            setBitmapResource(representation.getBitmapResource());
+            setNameResource(representation.getNameResource());
+        }
+    }
+
+    @Override
+    public boolean equals(FilterRepresentation representation) {
+        if (!super.equals(representation)) {
+            return false;
+        }
+        if (representation instanceof FilterFxRepresentation) {
+            FilterFxRepresentation fx = (FilterFxRepresentation) representation;
+            if (fx.mNameResource == mNameResource
+                    && fx.mBitmapResource == mBitmapResource) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean same(FilterRepresentation representation) {
+        if (!super.same(representation)) {
+            return false;
+        }
+        return equals(representation);
+    }
+
+    public boolean allowsMultipleInstances() {
+        return true;
+    }
+
+    public int getNameResource() {
+        return mNameResource;
+    }
+
+    public void setNameResource(int nameResource) {
+        mNameResource = nameResource;
+    }
+
+    public int getBitmapResource() {
+        return mBitmapResource;
+    }
+
+    public void setBitmapResource(int bitmapResource) {
+        mBitmapResource = bitmapResource;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java
new file mode 100644
index 0000000..f67254c
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterImageBorderRepresentation.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class FilterImageBorderRepresentation extends FilterRepresentation {
+    private int mDrawableResource = 0;
+
+    public FilterImageBorderRepresentation(int drawableResource) {
+        super("ImageBorder");
+        mDrawableResource = drawableResource;
+        setFilterClass(ImageFilterBorder.class);
+        setPriority(FilterRepresentation.TYPE_BORDER);
+        setTextId(R.string.borders);
+        setEditorId(ImageOnlyEditor.ID);
+        setShowEditingControls(false);
+        setShowParameterValue(false);
+        setShowUtilityPanel(false);
+    }
+
+    public String toString() {
+        return "FilterBorder: " + getName();
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterImageBorderRepresentation representation = (FilterImageBorderRepresentation) super.clone();
+        representation.setName(getName());
+        representation.setDrawableResource(getDrawableResource());
+        return representation;
+    }
+
+    public void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterImageBorderRepresentation) {
+            FilterImageBorderRepresentation representation = (FilterImageBorderRepresentation) a;
+            setName(representation.getName());
+            setDrawableResource(representation.getDrawableResource());
+        }
+    }
+
+    @Override
+    public boolean equals(FilterRepresentation representation) {
+        if (!super.equals(representation)) {
+            return false;
+        }
+        if (representation instanceof FilterImageBorderRepresentation) {
+            FilterImageBorderRepresentation border = (FilterImageBorderRepresentation) representation;
+            if (border.mDrawableResource == mDrawableResource) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int getTextId() {
+        return R.string.none;
+    }
+
+    public boolean allowsMultipleInstances() {
+        return true;
+    }
+
+    public int getDrawableResource() {
+        return mDrawableResource;
+    }
+
+    public void setDrawableResource(int drawableResource) {
+        mDrawableResource = drawableResource;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterPoint.java b/src/com/android/gallery3d/filtershow/filters/FilterPoint.java
new file mode 100644
index 0000000..4520717
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterPoint.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+public interface FilterPoint {
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java
new file mode 100644
index 0000000..fc01650
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterPointRepresentation.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import java.util.Vector;
+
+public abstract class FilterPointRepresentation extends FilterRepresentation {
+    private static final String LOGTAG = "FilterPointRepresentation";
+    private Vector<FilterPoint> mCandidates = new Vector<FilterPoint>();
+
+    public FilterPointRepresentation(String type, int textid, int editorID) {
+        super(type);
+        setFilterClass(ImageFilterRedEye.class);
+        setPriority(FilterRepresentation.TYPE_NORMAL);
+        setTextId(textid);
+        setEditorId(editorID);
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterPointRepresentation representation = (FilterPointRepresentation) super
+                .clone();
+        representation.mCandidates = (Vector<FilterPoint>) mCandidates.clone();
+        return representation;
+    }
+
+    public boolean hasCandidates() {
+        return mCandidates != null;
+    }
+
+    public Vector<FilterPoint> getCandidates() {
+        return mCandidates;
+    }
+
+    @Override
+    public boolean isNil() {
+        if (getCandidates() != null && getCandidates().size() > 0) {
+            return false;
+        }
+        return true;
+    }
+
+    public Object getCandidate(int index) {
+        return this.mCandidates.get(index);
+    }
+
+    public void addCandidate(FilterPoint c) {
+        this.mCandidates.add(c);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (a instanceof FilterPointRepresentation) {
+            FilterPointRepresentation representation = (FilterPointRepresentation) a;
+            mCandidates.clear();
+            for (FilterPoint redEyeCandidate : representation.mCandidates) {
+                mCandidates.add(redEyeCandidate);
+            }
+        }
+    }
+
+    public void removeCandidate(RedEyeCandidate c) {
+        this.mCandidates.remove(c);
+    }
+
+    public void clearCandidates() {
+        this.mCandidates.clear();
+    }
+
+    public int getNumberOfCandidates() {
+        return mCandidates.size();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
new file mode 100644
index 0000000..3f823ea
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
@@ -0,0 +1,53 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.graphics.RectF;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRedEye;
+
+import java.util.Vector;
+
+public class FilterRedEyeRepresentation extends FilterPointRepresentation {
+    private static final String LOGTAG = "FilterRedEyeRepresentation";
+
+    public FilterRedEyeRepresentation() {
+        super("RedEye",R.string.redeye,EditorRedEye.ID);
+        setFilterClass(ImageFilterRedEye.class);
+        setOverlayId(R.drawable.photoeditor_effect_redeye);
+        setOverlayOnly(true);
+    }
+
+    public void addRect(RectF rect, RectF bounds) {
+        Vector<RedEyeCandidate> intersects = new Vector<RedEyeCandidate>();
+        for (int i = 0; i < getCandidates().size(); i++) {
+            RedEyeCandidate r = (RedEyeCandidate) getCandidate(i);
+            if (r.intersect(rect)) {
+                intersects.add(r);
+            }
+        }
+        for (int i = 0; i < intersects.size(); i++) {
+            RedEyeCandidate r = intersects.elementAt(i);
+            rect.union(r.mRect);
+            bounds.union(r.mBounds);
+            removeCandidate(r);
+        }
+        addCandidate(new RedEyeCandidate(rect, bounds));
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
new file mode 100644
index 0000000..82012b9
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -0,0 +1,244 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.app.Log;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+
+public class FilterRepresentation implements Cloneable {
+    private static final String LOGTAG = "FilterRepresentation";
+    private static final boolean DEBUG = false;
+    private String mName;
+    private int mPriority = TYPE_NORMAL;
+    private Class mFilterClass;
+    private boolean mSupportsPartialRendering = false;
+    private int mTextId = 0;
+    private int mEditorId = BasicEditor.ID;
+    private int mButtonId = 0;
+    private int mOverlayId = 0;
+    private boolean mOverlayOnly = false;
+    private boolean mShowEditingControls = true;
+    private boolean mShowParameterValue = true;
+    private boolean mShowUtilityPanel = true;
+
+    public static final byte TYPE_BORDER = 1;
+    public static final byte TYPE_FX = 2;
+    public static final byte TYPE_WBALANCE = 3;
+    public static final byte TYPE_VIGNETTE = 4;
+    public static final byte TYPE_NORMAL = 5;
+    public static final byte TYPE_TINYPLANET = 6;
+
+    private FilterRepresentation mTempRepresentation = null;
+
+    public FilterRepresentation(String name) {
+        mName = name;
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterRepresentation representation = (FilterRepresentation) super.clone();
+        representation.setName(getName());
+        representation.setPriority(getPriority());
+        representation.setFilterClass(getFilterClass());
+        representation.setSupportsPartialRendering(supportsPartialRendering());
+        representation.setTextId(getTextId());
+        representation.setEditorId(getEditorId());
+        representation.setButtonId(getButtonId());
+        representation.setOverlayId(getOverlayId());
+        representation.setOverlayOnly(getOverlayOnly());
+        representation.setShowEditingControls(showEditingControls());
+        representation.setShowParameterValue(showParameterValue());
+        representation.setShowUtilityPanel(showUtilityPanel());
+        representation.mTempRepresentation =
+                mTempRepresentation != null ? mTempRepresentation.clone() : null;
+        if (DEBUG) {
+            Log.v(LOGTAG, "cloning from <" + this + "> to <" + representation + ">");
+        }
+        return representation;
+    }
+
+    public boolean equals(FilterRepresentation representation) {
+        if (representation == null) {
+            return false;
+        }
+        if (representation.mFilterClass == representation.mFilterClass
+                && representation.mName.equalsIgnoreCase(mName)
+                && representation.mPriority == mPriority
+                && representation.mSupportsPartialRendering == mSupportsPartialRendering
+                && representation.mTextId == mTextId
+                && representation.mEditorId == mEditorId
+                && representation.mButtonId == mButtonId
+                && representation.mOverlayId == mOverlayId
+                && representation.mOverlayOnly == mOverlayOnly
+                && representation.mShowEditingControls == mShowEditingControls
+                && representation.mShowParameterValue == mShowParameterValue
+                && representation.mShowUtilityPanel == mShowUtilityPanel) {
+            return true;
+        }
+        return false;
+    }
+
+    public String toString() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setPriority(int priority) {
+        mPriority = priority;
+    }
+
+    public int getPriority() {
+        return mPriority;
+    }
+
+    public boolean isNil() {
+        return false;
+    }
+
+    public boolean supportsPartialRendering() {
+        return false && mSupportsPartialRendering; // disable for now
+    }
+
+    public void setSupportsPartialRendering(boolean value) {
+        mSupportsPartialRendering = value;
+    }
+
+    public void useParametersFrom(FilterRepresentation a) {
+    }
+
+    public void clearTempRepresentation() {
+        mTempRepresentation = null;
+    }
+
+    public synchronized void updateTempParametersFrom(FilterRepresentation representation) {
+        if (mTempRepresentation == null) {
+            try {
+                mTempRepresentation = representation.clone();
+            } catch (CloneNotSupportedException e) {
+                e.printStackTrace();
+            }
+        } else {
+            mTempRepresentation.useParametersFrom(representation);
+        }
+    }
+
+    public synchronized void synchronizeRepresentation() {
+        if (mTempRepresentation != null) {
+            useParametersFrom(mTempRepresentation);
+        }
+    }
+
+    public boolean allowsMultipleInstances() {
+        return false;
+    }
+
+    public Class getFilterClass() {
+        return mFilterClass;
+    }
+
+    public void setFilterClass(Class filterClass) {
+        mFilterClass = filterClass;
+    }
+
+    public boolean same(FilterRepresentation b) {
+        if (b == null) {
+            return false;
+        }
+        return getFilterClass() == b.getFilterClass();
+    }
+
+    public int getTextId() {
+        return mTextId;
+    }
+
+    public void setTextId(int textId) {
+        mTextId = textId;
+    }
+
+    public int getButtonId() {
+        return mButtonId;
+    }
+
+    public void setButtonId(int buttonId) {
+        mButtonId = buttonId;
+    }
+
+    public int getOverlayId() {
+        return mOverlayId;
+    }
+
+    public void setOverlayId(int overlayId) {
+        mOverlayId = overlayId;
+    }
+
+    public boolean getOverlayOnly() {
+        return mOverlayOnly;
+    }
+
+    public void setOverlayOnly(boolean value) {
+        mOverlayOnly = value;
+    }
+
+    final public int getEditorId() {
+        return mEditorId;
+    }
+
+    public int[] getEditorIds() {
+        return new int[] {
+        mEditorId };
+    }
+
+    public void setEditorId(int editorId) {
+        mEditorId = editorId;
+    }
+
+    public boolean showEditingControls() {
+        return mShowEditingControls;
+    }
+
+    public void setShowEditingControls(boolean showEditingControls) {
+        mShowEditingControls = showEditingControls;
+    }
+
+    public boolean showParameterValue() {
+        return mShowParameterValue;
+    }
+
+    public void setShowParameterValue(boolean showParameterValue) {
+        mShowParameterValue = showParameterValue;
+    }
+
+    public boolean showUtilityPanel() {
+        return mShowUtilityPanel;
+    }
+
+    public void setShowUtilityPanel(boolean showUtilityPanel) {
+        mShowUtilityPanel = showUtilityPanel;
+    }
+
+    public String getStateRepresentation() {
+        return "";
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
new file mode 100644
index 0000000..ac5e046
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+
+public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation {
+    private static final String LOGTAG = "FilterTinyPlanetRepresentation";
+    private float mAngle = 0;
+
+    public FilterTinyPlanetRepresentation() {
+        super("TinyPlanet", 0, 50, 100);
+        setShowParameterValue(true);
+        setFilterClass(ImageFilterTinyPlanet.class);
+        setPriority(FilterRepresentation.TYPE_TINYPLANET);
+        setTextId(R.string.tinyplanet);
+        setButtonId(R.id.tinyplanetButton);
+        setEditorId(EditorTinyPlanet.ID);
+        setMinimum(1);
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterTinyPlanetRepresentation representation = (FilterTinyPlanetRepresentation) super
+                .clone();
+        representation.mAngle = mAngle;
+        representation.setZoom(getZoom());
+        return representation;
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        FilterTinyPlanetRepresentation representation = (FilterTinyPlanetRepresentation) a;
+        super.useParametersFrom(a);
+        mAngle = representation.mAngle;
+        setZoom(representation.getZoom());
+    }
+
+    public void setAngle(float angle) {
+        mAngle = angle;
+    }
+
+    public float getAngle() {
+        return mAngle;
+    }
+
+    public int getZoom() {
+        return getValue();
+    }
+
+    public void setZoom(int zoom) {
+        setValue(zoom);
+    }
+
+    public boolean isNil() {
+        // TinyPlanet always has an effect
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
new file mode 100644
index 0000000..eef54ef
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorVignette;
+import com.android.gallery3d.filtershow.imageshow.Oval;
+
+public class FilterVignetteRepresentation extends FilterBasicRepresentation implements Oval {
+    private static final String LOGTAG = "FilterVignetteRepresentation";
+    private float mCenterX = Float.NaN;
+    private float mCenterY;
+    private float mRadiusX = Float.NaN;
+    private float mRadiusY;
+
+    public FilterVignetteRepresentation() {
+        super("Vignette", -100, 50, 100);
+        setShowParameterValue(true);
+        setPriority(FilterRepresentation.TYPE_VIGNETTE);
+        setTextId(R.string.vignette);
+        setButtonId(R.id.vignetteEditor);
+        setEditorId(EditorVignette.ID);
+        setName("Vignette");
+        setFilterClass(ImageFilterVignette.class);
+
+        setMinimum(-100);
+        setMaximum(100);
+        setDefaultValue(0);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        super.useParametersFrom(a);
+        mCenterX = ((FilterVignetteRepresentation) a).mCenterX;
+        mCenterY = ((FilterVignetteRepresentation) a).mCenterY;
+        mRadiusX = ((FilterVignetteRepresentation) a).mRadiusX;
+        mRadiusY = ((FilterVignetteRepresentation) a).mRadiusY;
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        FilterVignetteRepresentation representation = (FilterVignetteRepresentation) super
+                .clone();
+        representation.mCenterX = mCenterX;
+        representation.mCenterY = mCenterY;
+
+        return representation;
+    }
+
+    @Override
+    public void setCenter(float centerX, float centerY) {
+        mCenterX = centerX;
+        mCenterY = centerY;
+    }
+
+    @Override
+    public float getCenterX() {
+        return mCenterX;
+    }
+
+    @Override
+    public float getCenterY() {
+        return mCenterY;
+    }
+
+    @Override
+    public void setRadius(float radiusX, float radiusY) {
+        mRadiusX = radiusX;
+        mRadiusY = radiusY;
+    }
+
+    @Override
+    public void setRadiusX(float radiusX) {
+        mRadiusX = radiusX;
+    }
+
+    @Override
+    public void setRadiusY(float radiusY) {
+        mRadiusY = radiusY;
+    }
+
+    @Override
+    public float getRadiusX() {
+        return mRadiusX;
+    }
+
+    @Override
+    public float getRadiusY() {
+        return mRadiusY;
+    }
+
+    public boolean isCenterSet() {
+        return mCenterX != Float.NaN;
+    }
+
+    @Override
+    public boolean isNil() {
+        return getValue() == 0;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/IconUtilities.java b/src/com/android/gallery3d/filtershow/filters/IconUtilities.java
new file mode 100644
index 0000000..38211f3
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/IconUtilities.java
@@ -0,0 +1,74 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import com.android.gallery3d.R;
+
+public class IconUtilities {
+    public static final int PUNCH = R.drawable.filtershow_fx_0005_punch;
+    public static final int VINTAGE = R.drawable.filtershow_fx_0000_vintage;
+    public static final int BW_CONTRAST = R.drawable.filtershow_fx_0004_bw_contrast;
+    public static final int BLEACH = R.drawable.filtershow_fx_0002_bleach;
+    public static final int INSTANT = R.drawable.filtershow_fx_0001_instant;
+    public static final int WASHOUT = R.drawable.filtershow_fx_0007_washout;
+    public static final int BLUECRUSH = R.drawable.filtershow_fx_0003_blue_crush;
+    public static final int WASHOUT_COLOR = R.drawable.filtershow_fx_0008_washout_color;
+    public static final int X_PROCESS = R.drawable.filtershow_fx_0006_x_process;
+
+    public static Bitmap getFXBitmap(Resources res, int id) {
+        Bitmap ret;
+        BitmapFactory.Options o = new BitmapFactory.Options();
+        o.inScaled = false;
+
+        if (id != 0) {
+            return BitmapFactory.decodeResource(res, id, o);
+        }
+        return null;
+    }
+
+    public static Bitmap loadBitmap(Resources res, int resource) {
+
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                res,
+                resource, options);
+
+        return bitmap;
+    }
+
+    public static Bitmap applyFX(Bitmap bitmap, final Bitmap fxBitmap) {
+        ImageFilterFx fx = new ImageFilterFx() {
+            @Override
+            public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+
+                int w = bitmap.getWidth();
+                int h = bitmap.getHeight();
+                int fxw = fxBitmap.getWidth();
+                int fxh = fxBitmap.getHeight();
+
+                nativeApplyFilter(bitmap, w, h, fxBitmap, fxw, fxh);
+                return bitmap;
+            }
+        };
+        return fx.apply(bitmap, 0, 0);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index 7f4d5ed..96ab84f 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -17,55 +17,45 @@
 package com.android.gallery3d.filtershow.filters;
 
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.support.v8.renderscript.Allocation;
+import android.widget.Toast;
 
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
+import com.android.gallery3d.filtershow.presets.FilterEnvironment;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-public class ImageFilter implements Cloneable {
-
-    protected int mMaxParameter = 100;
-    protected int mMinParameter = -100;
-    protected int mPreviewParameter = mMaxParameter;
-    protected int mDefaultParameter = 0;
-    protected int mParameter = 0;
-    private ImagePreset mImagePreset;
+public abstract class ImageFilter implements Cloneable {
+    private FilterEnvironment mEnvironment = null;
 
     protected String mName = "Original";
     private final String LOGTAG = "ImageFilter";
-    public static final byte TYPE_BORDER =1;
-    public static final byte TYPE_FX  = 2;
-    public static final byte TYPE_WBALANCE = 3;
-    public static final byte TYPE_VIGNETTE = 4;
-    public static final byte TYPE_NORMAL = 5;
-    public static final byte TYPE_TINYPLANET = 6;
-    private byte filterType = TYPE_NORMAL;
+    protected static final boolean SIMPLE_ICONS = true;
+    // TODO: Temporary, for dogfood note memory issues with toasts for better
+    // feedback. Remove this when filters actually work in low memory
+    // situations.
+    private static FilterShowActivity sActivity = null;
 
-    public byte getFilterType(){
-        return filterType;
+    public static void setActivityForMemoryToasts(FilterShowActivity activity) {
+        sActivity = activity;
     }
 
-    protected void setFilterType(byte type){
-        filterType = type;
+    public static void resetStatics() {
+        sActivity = null;
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilter filter = (ImageFilter) super.clone();
-        filter.setName(getName());
-        filter.setParameter(getParameter());
-        filter.setFilterType(filterType);
-        filter.mMaxParameter = mMaxParameter;
-        filter.mMinParameter = mMinParameter;
-        filter.mImagePreset = mImagePreset;
-        filter.mDefaultParameter = mDefaultParameter;
-        filter.mPreviewParameter = mPreviewParameter;
-        return filter;
-    }
+    public void freeResources() {}
 
-    public boolean isNil() {
-        if (mParameter == mDefaultParameter) {
-            return true;
+    public void displayLowMemoryToast() {
+        if (sActivity != null) {
+            sActivity.runOnUiThread(new Runnable() {
+                public void run() {
+                    Toast.makeText(sActivity, "Memory too low for filter " + getName() +
+                            ", please file a bug report", Toast.LENGTH_SHORT).show();
+                }
+            });
         }
-        return false;
     }
 
     public void setName(String name) {
@@ -76,70 +66,43 @@
         return mName;
     }
 
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public boolean supportsAllocationInput() { return false; }
+
+    public void apply(Allocation in, Allocation out) {
+    }
+
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
         // do nothing here, subclasses will implement filtering here
         return bitmap;
     }
 
-    public int getParameter() {
-        return mParameter;
-    }
-
-    public void setParameter(int value) {
-        mParameter = value;
-    }
-
-    /**
-     * The maximum allowed value (inclusive)
-     * @return maximum value allowed as input to this filter
-     */
-    public int getMaxParameter(){
-        return mMaxParameter;
-    }
-
-    /**
-     * The parameter value to be used in previews.
-     * @return parameter value to be used to preview the filter
-     */
-    public int getPreviewParameter(){
-        return mPreviewParameter;
-    }
-
-    /**
-     * The minimum allowed value (inclusive)
-     * @return minimum value allowed as input to this filter
-     */
-    public int getMinParameter(){
-        return mMinParameter;
-    }
-
-    /**
-     * Returns the default value returned by this filter.
-     * @return default value
-     */
-    public int getDefaultParameter(){
-        return mDefaultParameter;
-    }
-
     public ImagePreset getImagePreset() {
-        return mImagePreset;
+        return getEnvironment().getImagePreset();
     }
 
-    public void setImagePreset(ImagePreset mPreset) {
-        this.mImagePreset = mPreset;
-    }
-
-    public boolean same(ImageFilter filter) {
-        if (filter == null) {
-            return false;
-        }
-        if (!filter.getName().equalsIgnoreCase(getName())) {
-            return false;
-        }
-        return true;
-    }
+    public abstract void useRepresentation(FilterRepresentation representation);
 
     native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h,
             int[] redGradient, int[] greenGradient, int[] blueGradient);
 
+    public FilterRepresentation getDefaultRepresentation() {
+        return null;
+    }
+
+    protected Matrix getOriginalToScreenMatrix(int w, int h) {
+        GeometryMetadata geo = getImagePreset().mGeoData;
+        Matrix originalToScreen = geo.getOriginalToScreen(true,
+                getImagePreset().getImageLoader().getOriginalBounds().width(),
+                getImagePreset().getImageLoader().getOriginalBounds().height(),
+                w, h);
+        return originalToScreen;
+    }
+
+    public void setEnvironment(FilterEnvironment environment) {
+        mEnvironment = environment;
+    }
+
+    public FilterEnvironment getEnvironment() {
+        return mEnvironment;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
deleted file mode 100644
index f4a7717..0000000
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.filters;
-
-import android.graphics.Bitmap;
-
-public class ImageFilterBW extends ImageFilter {
-
-    public ImageFilterBW() {
-        mName = "Black & White";
-    }
-
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        int w = bitmap.getWidth();
-        int h = bitmap.getHeight();
-        nativeApplyFilter(bitmap, w, h);
-        return bitmap;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
deleted file mode 100644
index 45f4916..0000000
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.filters;
-
-import android.graphics.Bitmap;
-
-public class ImageFilterBWBlue extends ImageFilter {
-
-    public ImageFilterBWBlue() {
-        mName = "B&W - Blue";
-    }
-
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        int w = bitmap.getWidth();
-        int h = bitmap.getHeight();
-        nativeApplyFilter(bitmap, w, h);
-        return bitmap;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
deleted file mode 100644
index 8f91c3c..0000000
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.filters;
-
-import android.graphics.Bitmap;
-
-public class ImageFilterBWGreen extends ImageFilter {
-
-    public ImageFilterBWGreen() {
-        mName = "B&W - Green";
-    }
-
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        int w = bitmap.getWidth();
-        int h = bitmap.getHeight();
-        nativeApplyFilter(bitmap, w, h);
-        return bitmap;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
deleted file mode 100644
index f0c65d7..0000000
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.filters;
-
-import android.graphics.Bitmap;
-
-public class ImageFilterBWRed extends ImageFilter {
-
-    public ImageFilterBWRed() {
-        mName = "B&W - Red";
-    }
-
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        int w = bitmap.getWidth();
-        int h = bitmap.getHeight();
-        nativeApplyFilter(bitmap, w, h);
-        return bitmap;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
index 1d198e4..a7286f0 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBorder.java
@@ -16,71 +16,77 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 
+import java.util.HashMap;
+
 public class ImageFilterBorder extends ImageFilter {
-    Drawable mNinePatch = null;
+    private static final float NINEPATCH_ICON_SCALING = 10;
+    private static final float BITMAP_ICON_SCALING = 1 / 3.0f;
+    private FilterImageBorderRepresentation mParameters = null;
+    private Resources mResources = null;
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterBorder filter = (ImageFilterBorder) super.clone();
-        filter.setDrawable(mNinePatch);
-        return filter;
-    }
+    private HashMap<Integer, Drawable> mDrawables = new HashMap<Integer, Drawable>();
 
-    public ImageFilterBorder(Drawable ninePatch) {
-        setFilterType(TYPE_BORDER);
+    public ImageFilterBorder() {
         mName = "Border";
-        mNinePatch = ninePatch;
     }
 
-    @Override
-    public boolean isNil() {
-        if (mNinePatch == null) {
-            return  true;
-        }
-        return false;
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterImageBorderRepresentation parameters = (FilterImageBorderRepresentation) representation;
+        mParameters = parameters;
     }
 
-    @Override
-    public boolean same(ImageFilter filter) {
-        boolean isBorderFilter = super.same(filter);
-        if (!isBorderFilter) {
-            return false;
-        }
-        if (!(filter instanceof ImageFilterBorder)) {
-            return false;
-        }
-        ImageFilterBorder borderFilter = (ImageFilterBorder) filter;
-        if (mNinePatch != borderFilter.mNinePatch) {
-            return false;
-        }
-        return true;
+    public FilterImageBorderRepresentation getParameters() {
+        return mParameters;
     }
 
-    public void setDrawable(Drawable ninePatch) {
-        // TODO: for now we only use nine patch
-        mNinePatch = ninePatch;
+    public void freeResources() {
+       mDrawables.clear();
     }
 
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        if (mNinePatch == null) {
-            return bitmap;
-        }
-
+    public Bitmap applyHelper(Bitmap bitmap, float scale1, float scale2 ) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-
-        float scale = scaleFactor * 2.0f;
-        Rect bounds = new Rect(0, 0, (int) (w / scale), (int) (h / scale));
+        Rect bounds = new Rect(0, 0, (int) (w * scale1), (int) (h * scale1));
         Canvas canvas = new Canvas(bitmap);
-        canvas.scale(scale, scale);
-        mNinePatch.setBounds(bounds);
-        mNinePatch.draw(canvas);
+        canvas.scale(scale2, scale2);
+        Drawable drawable = getDrawable(getParameters().getDrawableResource());
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
         return bitmap;
     }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null || getParameters().getDrawableResource() == 0) {
+            return bitmap;
+        }
+        float scale2 = scaleFactor * 2.0f;
+        float scale1 = 1 / scale2;
+        return applyHelper(bitmap, scale1, scale2);
+    }
+
+    public void setResources(Resources resources) {
+        if (mResources != resources) {
+            mResources = resources;
+            mDrawables.clear();
+        }
+    }
+
+    public Drawable getDrawable(int rsc) {
+        Drawable drawable = mDrawables.get(rsc);
+        if (drawable == null && mResources != null && rsc != 0) {
+            drawable = new BitmapDrawable(mResources, BitmapFactory.decodeResource(mResources, rsc));
+            mDrawables.put(rsc, drawable);
+        }
+        return drawable;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
index 558abe3..a4626cd 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
@@ -16,32 +16,41 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 import android.graphics.Color;
 
 
-public class ImageFilterBwFilter extends ImageFilter {
+public class ImageFilterBwFilter extends SimpleImageFilter {
 
     public ImageFilterBwFilter() {
         mName = "BW Filter";
-        mMaxParameter = 180;
-        mMinParameter = -180;
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterBwFilter filter = (ImageFilterBwFilter) super.clone();
-        return filter;
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("BW Filter");
+        representation.setFilterClass(ImageFilterBwFilter.class);
+        representation.setMaximum(180);
+        representation.setMinimum(-180);
+        representation.setTextId(R.string.bwfilter);
+        representation.setButtonId(R.id.bwfilterButton);
+        representation.setSupportsPartialRendering(true);
+        return representation;
     }
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int r, int g, int b);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
         float[] hsv = new float[] {
-                180 + mParameter, 1, 1
+                180 + getParameters().getValue(), 1, 1
         };
         int rgb = Color.HSVToColor(hsv);
         int r = 0xFF & (rgb >> 16);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
index 0c3bb37..2097f0d 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
@@ -16,22 +16,41 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterContrast extends ImageFilter {
+public class ImageFilterContrast extends SimpleImageFilter {
 
     public ImageFilterContrast() {
         mName = "Contrast";
     }
 
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Contrast");
+        representation.setFilterClass(ImageFilterContrast.class);
+        representation.setTextId(R.string.contrast);
+        representation.setButtonId(R.id.contrastButton);
+
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        float p = mParameter;
-        float value = p;
+        float value = getParameters().getValue();
         nativeApplyFilter(bitmap, w, h, value);
         return bitmap;
     }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
index 89641d1..daa1cd3 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java
@@ -23,51 +23,26 @@
 public class ImageFilterCurves extends ImageFilter {
 
     private static final String LOGTAG = "ImageFilterCurves";
-    private final Spline[] mSplines = new Spline[4];
+    FilterCurvesRepresentation mParameters = new FilterCurvesRepresentation();
+
+    @Override
+    public FilterRepresentation getDefaultRepresentation() {
+        return new FilterCurvesRepresentation();
+    }
+
+    @Override
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterCurvesRepresentation parameters = (FilterCurvesRepresentation) representation;
+        mParameters = parameters;
+    }
 
     public ImageFilterCurves() {
         mName = "Curves";
         reset();
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterCurves filter = (ImageFilterCurves) super.clone();
-        for (int i = 0; i < 4; i++) {
-            if (mSplines[i] != null) {
-                filter.setSpline(mSplines[i], i);
-            }
-        }
-        return filter;
-    }
-
-    @Override
-    public boolean isNil() {
-        for (int i = 0; i < 4; i++) {
-            if (mSplines[i] != null && !mSplines[i].isOriginal()) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean same(ImageFilter filter) {
-        boolean isCurveFilter = super.same(filter);
-        if (!isCurveFilter) {
-            return false;
-        }
-        ImageFilterCurves curve = (ImageFilterCurves) filter;
-        for (int i = 0; i < 4; i++) {
-            if (mSplines[i] != curve.mSplines[i]) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     public void populateArray(int[] array, int curveIndex) {
-        Spline spline = mSplines[curveIndex];
+        Spline spline = mParameters.getSpline(curveIndex);
         if (spline == null) {
             return;
         }
@@ -78,8 +53,8 @@
     }
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        if (!mSplines[Spline.RGB].isOriginal()) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (!mParameters.getSpline(Spline.RGB).isOriginal()) {
             int[] rgbGradient = new int[256];
             populateArray(rgbGradient, Spline.RGB);
             nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
@@ -87,17 +62,17 @@
         }
 
         int[] redGradient = null;
-        if (!mSplines[Spline.RED].isOriginal()) {
+        if (!mParameters.getSpline(Spline.RED).isOriginal()) {
             redGradient = new int[256];
             populateArray(redGradient, Spline.RED);
         }
         int[] greenGradient = null;
-        if (!mSplines[Spline.GREEN].isOriginal()) {
+        if (!mParameters.getSpline(Spline.GREEN).isOriginal()) {
             greenGradient = new int[256];
             populateArray(greenGradient, Spline.GREEN);
         }
         int[] blueGradient = null;
-        if (!mSplines[Spline.BLUE].isOriginal()) {
+        if (!mParameters.getSpline(Spline.BLUE).isOriginal()) {
             blueGradient = new int[256];
             populateArray(blueGradient, Spline.BLUE);
         }
@@ -108,11 +83,11 @@
     }
 
     public void setSpline(Spline spline, int splineIndex) {
-        mSplines[splineIndex] = new Spline(spline);
+        mParameters.setSpline(splineIndex, new Spline(spline));
     }
 
     public Spline getSpline(int splineIndex) {
-        return mSplines[splineIndex];
+        return mParameters.getSpline(splineIndex);
     }
 
     public void reset() {
@@ -122,7 +97,16 @@
         spline.addPoint(1.0f, 0.0f);
 
         for (int i = 0; i < 4; i++) {
-            mSplines[i] = new Spline(spline);
+            mParameters.setSpline(i, new Spline(spline));
+        }
+    }
+
+    public void useFilter(ImageFilter a) {
+        ImageFilterCurves c = (ImageFilterCurves) a;
+        for (int i = 0; i < 4; i++) {
+            if (c.mParameters.getSpline(i) != null) {
+                setSpline(c.mParameters.getSpline(i), i);
+            }
         }
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
new file mode 100644
index 0000000..0b02fc4
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.ImageLoader;
+
+public class ImageFilterDownsample extends SimpleImageFilter {
+    private static final int ICON_DOWNSAMPLE_FRACTION = 8;
+    private ImageLoader mImageLoader;
+
+    public ImageFilterDownsample(ImageLoader loader) {
+        mName = "Downsample";
+        mImageLoader = loader;
+    }
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Downsample");
+        representation.setFilterClass(ImageFilterDownsample.class);
+        representation.setMaximum(100);
+        representation.setMinimum(1);
+        representation.setValue(50);
+        representation.setDefaultValue(50);
+        representation.setPreviewValue(3);
+        representation.setTextId(R.string.downsample);
+        representation.setButtonId(R.id.downsampleButton);
+        return representation;
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        int p = getParameters().getValue();
+
+        // size of original precached image
+        Rect size = mImageLoader.getOriginalBounds();
+        int orig_w = size.width();
+        int orig_h = size.height();
+
+        if (p > 0 && p < 100) {
+            // scale preview to same size as the resulting bitmap from a "save"
+            int newWidth = orig_w * p / 100;
+            int newHeight = orig_h * p / 100;
+
+            // only scale preview if preview isn't already scaled enough
+            if (newWidth <= 0 || newHeight <= 0 || newWidth >= w || newHeight >= h) {
+                return bitmap;
+            }
+            Bitmap ret = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+            if (ret != bitmap) {
+                bitmap.recycle();
+            }
+            return ret;
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
new file mode 100644
index 0000000..1fd9071
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+
+import java.util.Vector;
+
+public class ImageFilterDraw extends ImageFilter {
+    private static final String LOGTAG = "ImageFilterDraw";
+    public final static byte SIMPLE_STYLE = 0;
+    public final static byte BRUSH_STYLE_SPATTER = 1;
+    public final static byte BRUSH_STYLE_MARKER = 2;
+    public final static int NUMBER_OF_STYLES = 3;
+    Bitmap mOverlayBitmap; // this accelerates interaction
+    int mCachedStrokes = -1;
+    int mCurrentStyle = 0;
+
+    FilterDrawRepresentation mParameters = new FilterDrawRepresentation();
+
+    public ImageFilterDraw() {
+        mName = "Image Draw";
+    }
+
+    DrawStyle[] mDrawingsTypes = new DrawStyle[] {
+            new SimpleDraw(),
+            new Brush(R.drawable.brush_marker),
+            new Brush(R.drawable.brush_spatter)
+    };
+    {
+        for (int i = 0; i < mDrawingsTypes.length; i++) {
+            mDrawingsTypes[i].setType((byte) i);
+        }
+
+    }
+
+    @Override
+    public FilterRepresentation getDefaultRepresentation() {
+        return new FilterDrawRepresentation();
+    }
+
+    @Override
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterDrawRepresentation parameters = (FilterDrawRepresentation) representation;
+        mParameters = parameters;
+    }
+
+    public void setStyle(byte style) {
+        mCurrentStyle = style % mDrawingsTypes.length;
+    }
+
+    public int getStyle() {
+        return mCurrentStyle;
+    }
+
+    public static interface DrawStyle {
+        public void setType(byte type);
+        public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+                int quality);
+    }
+
+    class SimpleDraw implements DrawStyle {
+        byte mType;
+
+        @Override
+        public void setType(byte type) {
+            mType = type;
+        }
+
+        @Override
+        public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+                int quality) {
+            if (sd == null) {
+                return;
+            }
+            if (sd.mPath == null) {
+                return;
+            }
+            Paint paint = new Paint();
+
+            paint.setStyle(Style.STROKE);
+            paint.setColor(sd.mColor);
+            paint.setStrokeWidth(toScrMatrix.mapRadius(sd.mRadius));
+
+            // done this way because of a bug in path.transform(matrix)
+            Path mCacheTransPath = new Path();
+            mCacheTransPath.addPath(sd.mPath, toScrMatrix);
+
+            canvas.drawPath(mCacheTransPath, paint);
+        }
+    }
+
+    class Brush implements DrawStyle {
+        int mBrushID;
+        Bitmap mBrush;
+        byte mType;
+
+        public Brush(int brushID) {
+            mBrushID = brushID;
+        }
+        public Bitmap getBrush() {
+            if (mBrush == null) {
+                BitmapFactory.Options opt = new BitmapFactory.Options();
+                opt.inPreferredConfig = Bitmap.Config.ALPHA_8;
+                mBrush = MasterImage.getImage().getImageLoader().decodeImage(mBrushID, opt);
+                mBrush = mBrush.extractAlpha();
+            }
+            return mBrush;
+        }
+
+        @Override
+        public void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+                int quality) {
+            if (sd == null || sd.mPath == null) {
+                return;
+            }
+            Paint paint = new Paint();
+            paint.setStyle(Style.STROKE);
+            paint.setAntiAlias(true);
+            Path mCacheTransPath = new Path();
+            mCacheTransPath.addPath(sd.mPath, toScrMatrix);
+            draw(canvas, paint, sd.mColor, toScrMatrix.mapRadius(sd.mRadius) * 2,
+                    mCacheTransPath);
+        }
+
+        public Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
+        {
+            Matrix m = new Matrix();
+            m.setScale(dstWidth / (float) src.getWidth(), dstHeight / (float) src.getHeight());
+            Bitmap result = Bitmap.createBitmap(dstWidth, dstHeight, src.getConfig());
+            Canvas canvas = new Canvas(result);
+
+            Paint paint = new Paint();
+            paint.setFilterBitmap(filter);
+            canvas.drawBitmap(src, m, paint);
+
+            return result;
+
+        }
+        void draw(Canvas canvas, Paint paint, int color, float size, Path path) {
+            PathMeasure mPathMeasure = new PathMeasure();
+            float[] mPosition = new float[2];
+            float[] mTan = new float[2];
+
+            mPathMeasure.setPath(path, false);
+
+            paint.setAntiAlias(true);
+            paint.setColor(color);
+
+            paint.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY));
+            Bitmap brush;
+            // done this way because of a bug in
+            // Bitmap.createScaledBitmap(getBrush(),(int) size,(int) size,true);
+            brush = createScaledBitmap(getBrush(), (int) size, (int) size, true);
+            float len = mPathMeasure.getLength();
+            float s2 = size / 2;
+            float step = s2 / 8;
+            for (float i = 0; i < len; i += step) {
+                mPathMeasure.getPosTan(i, mPosition, mTan);
+                //                canvas.drawCircle(pos[0], pos[1], size, paint);
+                canvas.drawBitmap(brush, mPosition[0] - s2, mPosition[1] - s2, paint);
+            }
+        }
+
+        @Override
+        public void setType(byte type) {
+            mType = type;
+        }
+    }
+
+    void paint(FilterDrawRepresentation.StrokeData sd, Canvas canvas, Matrix toScrMatrix,
+            int quality) {
+        mDrawingsTypes[sd.mType].paint(sd, canvas, toScrMatrix, quality);
+    }
+
+    public void drawData(Canvas canvas, Matrix originalRotateToScreen, int quality) {
+        Paint paint = new Paint();
+        if (quality == ImagePreset.QUALITY_FINAL) {
+            paint.setAntiAlias(true);
+        }
+        paint.setStyle(Style.STROKE);
+        paint.setColor(Color.RED);
+        paint.setStrokeWidth(40);
+
+        if (mParameters.getDrawing().isEmpty() && mParameters.getCurrentDrawing() == null) {
+            return;
+        }
+        if (quality == ImagePreset.QUALITY_FINAL) {
+            for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
+                paint(strokeData, canvas, originalRotateToScreen, quality);
+            }
+            return;
+        }
+
+        if (mOverlayBitmap == null ||
+                mOverlayBitmap.getWidth() != canvas.getWidth() ||
+                mOverlayBitmap.getHeight() != canvas.getHeight() ||
+                mParameters.getDrawing().size() < mCachedStrokes) {
+
+            mOverlayBitmap = Bitmap.createBitmap(
+                    canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
+            mCachedStrokes = 0;
+        }
+
+        if (mCachedStrokes < mParameters.getDrawing().size()) {
+            fillBuffer(originalRotateToScreen);
+        }
+        canvas.drawBitmap(mOverlayBitmap, 0, 0, paint);
+
+        StrokeData stroke = mParameters.getCurrentDrawing();
+        if (stroke != null) {
+            paint(stroke, canvas, originalRotateToScreen, quality);
+        }
+    }
+
+    public void fillBuffer(Matrix originalRotateToScreen) {
+        Canvas drawCache = new Canvas(mOverlayBitmap);
+        Vector<FilterDrawRepresentation.StrokeData> v = mParameters.getDrawing();
+        int n = v.size();
+
+        for (int i = mCachedStrokes; i < n; i++) {
+            paint(v.get(i), drawCache, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+        }
+        mCachedStrokes = n;
+    }
+
+    public void draw(Canvas canvas, Matrix originalRotateToScreen) {
+        for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
+            paint(strokeData, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+        }
+        mDrawingsTypes[mCurrentStyle].paint(
+                null, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        Matrix m = getOriginalToScreenMatrix(w, h);
+        drawData(new Canvas(bitmap), m, quality);
+        return bitmap;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
new file mode 100644
index 0000000..46a9a29
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterEdge extends SimpleImageFilter {
+
+    public ImageFilterEdge() {
+        mName = "Edge";
+    }
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterRepresentation representation = super.getDefaultRepresentation();
+        representation.setName("Edge");
+        representation.setFilterClass(ImageFilterEdge.class);
+        representation.setTextId(R.string.edge);
+        representation.setButtonId(R.id.edgeButton);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float p);
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        float p = getParameters().getValue() + 101;
+        p = (float) p / 100;
+        nativeApplyFilter(bitmap, w, h, p);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
index e38dc8e..b0b0b2d 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
@@ -16,22 +16,40 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterExposure extends ImageFilter {
+public class ImageFilterExposure extends SimpleImageFilter {
 
     public ImageFilterExposure() {
         mName = "Exposure";
     }
 
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Exposure");
+        representation.setFilterClass(ImageFilterExposure.class);
+        representation.setTextId(R.string.exposure);
+        representation.setButtonId(R.id.exposureButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        int p = mParameter;
-        float value = p;
+        float value = getParameters().getValue();
         nativeApplyFilter(bitmap, w, h, value);
         return bitmap;
     }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
index 04d7641..68e8a7c 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
@@ -16,46 +16,75 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import com.android.gallery3d.app.Log;
 
 public class ImageFilterFx extends ImageFilter {
-    private static final String TAG = "ImageFilterFx";
-    Bitmap fxBitmap;
-    public ImageFilterFx(Bitmap fxBitmap,String name) {
-        setFilterType(TYPE_FX);
-        mName = name;
-        this.fxBitmap = fxBitmap;
+    private static final String LOGTAG = "ImageFilterFx";
+    private FilterFxRepresentation mParameters = null;
+    private Bitmap mFxBitmap = null;
+    private Resources mResources = null;
+    private int mFxBitmapId = 0;
+
+    public ImageFilterFx() {
     }
 
     @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterFx filter = (ImageFilterFx) super.clone();
-        filter.fxBitmap = this.fxBitmap;
-        return filter;
+    public void freeResources() {
+        if (mFxBitmap != null) mFxBitmap.recycle();
+        mFxBitmap = null;
     }
 
-    @Override
-    public boolean isNil() {
-        if (fxBitmap != null) {
-            return false;
-        }
-        return true;
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterFxRepresentation parameters = (FilterFxRepresentation) representation;
+        mParameters = parameters;
+    }
+
+    public FilterFxRepresentation getParameters() {
+        return mParameters;
     }
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h,Bitmap  fxBitmap, int fxw, int fxh);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        if (fxBitmap==null)
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null || mResources == null) {
             return bitmap;
+        }
 
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
 
-        int fxw = fxBitmap.getWidth();
-        int fxh = fxBitmap.getHeight();
+        int bitmapResourceId = getParameters().getBitmapResource();
+        if (bitmapResourceId == 0) { // null filter fx
+            return bitmap;
+        }
 
-        nativeApplyFilter(bitmap, w, h,   fxBitmap,  fxw,  fxh);
+        if (mFxBitmap == null || mFxBitmapId != bitmapResourceId) {
+            BitmapFactory.Options o = new BitmapFactory.Options();
+            o.inScaled = false;
+            mFxBitmapId = bitmapResourceId;
+            if (mFxBitmapId != 0) {
+                mFxBitmap = BitmapFactory.decodeResource(mResources, mFxBitmapId, o);
+            } else {
+                Log.w(LOGTAG, "bad resource for filter: " + mName);
+            }
+        }
+
+        if (mFxBitmap == null) {
+            return bitmap;
+        }
+
+        int fxw = mFxBitmap.getWidth();
+        int fxh = mFxBitmap.getHeight();
+
+        nativeApplyFilter(bitmap, w, h, mFxBitmap, fxw, fxh);
         return bitmap;
     }
+
+    public void setResources(Resources resources) {
+        mResources = resources;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index d74a6fa..4f46eed 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -22,7 +22,9 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.Log;
 
+import com.android.gallery3d.filtershow.crop.CropExtras;
 import com.android.gallery3d.filtershow.imageshow.GeometryMath;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
 
@@ -44,14 +46,11 @@
 
     @Override
     public ImageFilter clone() throws CloneNotSupportedException {
+        // FIXME: clone() should not be needed. Remove when we fix geometry.
         ImageFilterGeometry filter = (ImageFilterGeometry) super.clone();
         return filter;
     }
 
-    public void setGeometryMetadata(GeometryMetadata m) {
-        mGeometry = m;
-    }
-
     native protected void nativeApplyFilterFlip(Bitmap src, int srcWidth, int srcHeight,
             Bitmap dst, int dstWidth, int dstHeight, int flip);
 
@@ -65,26 +64,70 @@
             Bitmap dst, int dstWidth, int dstHeight, float straightenAngle);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public void useRepresentation(FilterRepresentation representation) {
+        mGeometry = (GeometryMetadata) representation;
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
         // TODO: implement bilinear or bicubic here... for now, just use
         // canvas to do a simple implementation...
         // TODO: and be more memory efficient! (do it in native?)
+        RectF cb = mGeometry.getPreviewCropBounds();
+        RectF pb = mGeometry.getPhotoBounds();
+        if (cb.width() == 0 || cb.height() == 0 || pb.width() == 0 || pb.height() == 0) {
+            Log.w(LOGTAG, "Cannot apply geometry: geometry metadata has not been initialized");
+            return bitmap;
+        }
+        CropExtras extras = mGeometry.getCropExtras();
+        boolean useExtras = mGeometry.getUseCropExtrasFlag();
+        int outputX = 0;
+        int outputY = 0;
+        boolean s = false;
+        if (extras != null && useExtras){
+            outputX = extras.getOutputX();
+            outputY = extras.getOutputY();
+            s = extras.getScaleUp();
+        }
+
+
         Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
         RectF crop = mGeometry.getCropBounds(bitmap);
         if (crop.width() > 0 && crop.height() > 0)
             cropBounds = GeometryMath.roundNearest(crop);
-        Bitmap temp = null;
-        if (mGeometry.hasSwitchedWidthHeight()) {
-            temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig);
-        } else {
-            temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig);
+
+        int width = cropBounds.width();
+        int height = cropBounds.height();
+
+        if (mGeometry.hasSwitchedWidthHeight()){
+            int temp = width;
+            width = height;
+            height = temp;
         }
+
+        if(outputX <= 0 || outputY <= 0){
+            outputX = width;
+            outputY = height;
+        }
+
+        float scaleX = 1;
+        float scaleY = 1;
+        if (s){
+                scaleX = (float) outputX / width;
+                scaleY = (float) outputY / height;
+        }
+
+        Bitmap temp = null;
+        temp = Bitmap.createBitmap(outputX, outputY, mConfig);
+
         float[] displayCenter = {
                 temp.getWidth() / 2f, temp.getHeight() / 2f
         };
 
         Matrix m1 = mGeometry.buildTotalXform(bitmap.getWidth(), bitmap.getHeight(), displayCenter);
 
+        m1.postScale(scaleX, scaleY, displayCenter[0], displayCenter[1]);
+
         Canvas canvas = new Canvas(temp);
         Paint paint = new Paint();
         paint.setAntiAlias(true);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
deleted file mode 100644
index be4ba87..0000000
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGradient.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.filters;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.Shader.TileMode;
-
-public class ImageFilterGradient extends ImageFilter {
-
-    private Bitmap mGradientBitmap = null;
-    private int[] mColors = null;
-    private float[] mPositions = null;
-
-    public ImageFilterGradient() {
-        mName = "Gradient";
-    }
-
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterGradient filter = (ImageFilterGradient) super.clone();
-        System.arraycopy(mColors, 0, filter.mColors, 0, mColors.length);
-        System.arraycopy(mPositions, 0, filter.mPositions, 0, mPositions.length);
-        return filter;
-    }
-
-    public void addColor(int color, float position) {
-        int length = 0;
-        if (mColors != null) {
-            length = mColors.length;
-        }
-        int[] colors = new int[length + 1];
-        float[] positions = new float[length + 1];
-
-        for (int i = 0; i < length; i++) {
-            colors[i] = mColors[i];
-            positions[i] = mPositions[i];
-        }
-
-        colors[length] = color;
-        positions[length] = position;
-
-        mColors = colors;
-        mPositions = positions;
-    }
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        createGradient();
-        int[] gradient = new int[256];
-        int[] redGradient = new int[256];
-        int[] greenGradient = new int[256];
-        int[] blueGradient = new int[256];
-        mGradientBitmap.getPixels(gradient, 0, 256, 0, 0, 256, 1);
-
-        for (int i = 0; i < 256; i++) {
-            redGradient[i] = Color.red(gradient[i]);
-            greenGradient[i] = Color.green(gradient[i]);
-            blueGradient[i] = Color.blue(gradient[i]);
-        }
-        nativeApplyGradientFilter(bitmap, bitmap.getWidth(), bitmap.getHeight(),
-                redGradient, greenGradient, blueGradient);
-        return bitmap;
-    }
-
-    public void createGradient() {
-        if (mGradientBitmap != null) {
-            return;
-        }
-
-        /* Create a 200 x 200 bitmap and fill it with black. */
-        Bitmap b = Bitmap.createBitmap(256, 1, Config.ARGB_8888);
-        Canvas c = new Canvas(b);
-        c.drawColor(Color.BLACK);
-
-        /* Create your gradient. */
-
-        /*
-         * int[] colors = new int[2]; colors[0] = Color.argb(255, 20, 20, 10);
-         * colors[0] = Color.BLACK; colors[1] = Color.argb(255, 228, 231, 193);
-         * float[] positions = new float[2]; positions[0] = 0; positions[1] = 1;
-         */
-
-        LinearGradient grad = new LinearGradient(0, 0, 255, 1, mColors,
-                mPositions, TileMode.CLAMP);
-
-        /* Draw your gradient to the top of your bitmap. */
-        Paint p = new Paint();
-        p.setStyle(Style.FILL);
-        p.setShader(grad);
-        c.drawRect(0, 0, 256, 1, p);
-        mGradientBitmap = b;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
new file mode 100644
index 0000000..0022a9e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
@@ -0,0 +1,73 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterHighlights extends SimpleImageFilter {
+    private static final String LOGTAG = "ImageFilterVignette";
+
+    public ImageFilterHighlights() {
+        mName = "Highlights";
+    }
+
+    SplineMath mSpline = new SplineMath(5);
+    double[] mHighlightCurve = { 0.0, 0.32, 0.418, 0.476, 0.642 };
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Shadows");
+        representation.setFilterClass(ImageFilterHighlights.class);
+        representation.setTextId(R.string.highlight_recovery);
+        representation.setButtonId(R.id.highlightRecoveryButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float[] luminanceMap);
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
+        float p = getParameters().getValue();
+        double t = p/100.;
+        for (int i = 0; i < 5; i++) {
+            double x = i / 4.;
+            double y = mHighlightCurve[i] *t+x*(1-t);
+            mSpline.setPoint(i, x, y);
+        }
+
+        float[][] curve = mSpline.calculatetCurve(256);
+        float[] luminanceMap = new float[curve.length];
+        for (int i = 0; i < luminanceMap.length; i++) {
+            luminanceMap[i] = curve[i][1];
+        }
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        nativeApplyFilter(bitmap, w, h, luminanceMap);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
index 279718e..b1f9f73 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
@@ -16,33 +16,43 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterHue extends ImageFilter {
+public class ImageFilterHue extends SimpleImageFilter {
     private ColorSpaceMatrix cmatrix = null;
 
     public ImageFilterHue() {
         mName = "Hue";
         cmatrix = new ColorSpaceMatrix();
-        mMaxParameter = 180;
-        mMinParameter = -180;
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterHue filter = (ImageFilterHue) super.clone();
-        filter.cmatrix = new ColorSpaceMatrix(cmatrix);
-        return filter;
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Hue");
+        representation.setFilterClass(ImageFilterHue.class);
+        representation.setMinimum(-180);
+        representation.setMaximum(180);
+        representation.setTextId(R.string.hue);
+        representation.setButtonId(R.id.hueButton);
+        representation.setEditorId(BasicEditor.ID);
+        representation.setSupportsPartialRendering(true);
+        return representation;
     }
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float []matrix);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        float p = mParameter;
-        float value = p;
+        float value = getParameters().getValue();
         cmatrix.identity();
         cmatrix.setHue(value);
 
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
new file mode 100644
index 0000000..29e6d16
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.text.format.Time;
+
+import com.android.gallery3d.R;
+
+public class ImageFilterKMeans extends SimpleImageFilter {
+    private int mSeed = 0;
+
+    public ImageFilterKMeans() {
+        mName = "KMeans";
+
+        // set random seed for session
+        Time t = new Time();
+        t.setToNow();
+        mSeed = (int) t.toMillis(false);
+    }
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("KMeans");
+        representation.setFilterClass(ImageFilterKMeans.class);
+        representation.setMaximum(20);
+        representation.setMinimum(2);
+        representation.setValue(4);
+        representation.setDefaultValue(4);
+        representation.setPreviewValue(4);
+        representation.setTextId(R.string.kmeans);
+        representation.setButtonId(R.id.kmeansButton);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int width, int height,
+            Bitmap large_ds_bm, int lwidth, int lheight, Bitmap small_ds_bm,
+            int swidth, int sheight, int p, int seed);
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+
+        Bitmap large_bm_ds = bitmap;
+        Bitmap small_bm_ds = bitmap;
+
+        // find width/height for larger downsampled bitmap
+        int lw = w;
+        int lh = h;
+        while (lw > 256 && lh > 256) {
+            lw /= 2;
+            lh /= 2;
+        }
+        if (lw != w) {
+            large_bm_ds = Bitmap.createScaledBitmap(bitmap, lw, lh, true);
+        }
+
+        // find width/height for smaller downsampled bitmap
+        int sw = lw;
+        int sh = lh;
+        while (sw > 64 && sh > 64) {
+            sw /= 2;
+            sh /= 2;
+        }
+        if (sw != lw) {
+            small_bm_ds = Bitmap.createScaledBitmap(large_bm_ds, sw, sh, true);
+        }
+
+        if (getParameters() != null) {
+            int p = Math.max(getParameters().getValue(), getParameters().getMinimum()) % (getParameters().getMaximum() + 1);
+            nativeApplyFilter(bitmap, w, h, large_bm_ds, lw, lh, small_bm_ds, sw, sh, p, mSeed);
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
new file mode 100644
index 0000000..c256686
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
@@ -0,0 +1,40 @@
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
+public class ImageFilterNegative extends ImageFilter {
+
+    public ImageFilterNegative() {
+        mName = "Negative";
+    }
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterRepresentation representation = new FilterDirectRepresentation("Negative");
+        representation.setFilterClass(ImageFilterNegative.class);
+        representation.setTextId(R.string.negative);
+        representation.setButtonId(R.id.negativeButton);
+        representation.setShowEditingControls(false);
+        representation.setShowParameterValue(false);
+        representation.setEditorId(ImageOnlyEditor.ID);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h);
+
+    @Override
+    public void useRepresentation(FilterRepresentation representation) {
+
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        nativeApplyFilter(bitmap, w, h);
+        return bitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
index ade3cb2..25e5d14 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
@@ -18,75 +18,34 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.RectF;
 
 public class ImageFilterParametricBorder extends ImageFilter {
-    private int mBorderColor = Color.WHITE;
-    private int mBorderSize = 10;
-    private int mBorderCornerRadius = 10;
+    private FilterColorBorderRepresentation mParameters = null;
 
     public ImageFilterParametricBorder() {
-        setFilterType(TYPE_BORDER);
         mName = "Border";
     }
 
-    public ImageFilterParametricBorder(int color, int size, int radius) {
-        setBorder(color, size, radius);
-        setFilterType(TYPE_BORDER);
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterColorBorderRepresentation parameters = (FilterColorBorderRepresentation) representation;
+        mParameters = parameters;
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterParametricBorder filter = (ImageFilterParametricBorder) super.clone();
-        filter.setBorder(mBorderColor, mBorderSize, mBorderCornerRadius);
-        return filter;
+    public FilterColorBorderRepresentation getParameters() {
+        return mParameters;
     }
 
-    @Override
-    public boolean isNil() {
-        return false;
-    }
-
-    @Override
-    public boolean same(ImageFilter filter) {
-        boolean isBorderFilter = super.same(filter);
-        if (!isBorderFilter) {
-            return false;
+    private void applyHelper(Canvas canvas, int w, int h) {
+        if (getParameters() == null) {
+            return;
         }
-        if (!(filter instanceof ImageFilterParametricBorder)) {
-            return false;
-        }
-        ImageFilterParametricBorder borderFilter = (ImageFilterParametricBorder) filter;
-        if (borderFilter.mBorderColor != mBorderColor) {
-            return false;
-        }
-        if (borderFilter.mBorderSize != mBorderSize) {
-            return false;
-        }
-        if (borderFilter.mBorderCornerRadius != mBorderCornerRadius) {
-            return false;
-        }
-        return true;
-    }
-
-    public void setBorder(int color, int size, int radius) {
-        mBorderColor = color;
-        mBorderSize = size;
-        mBorderCornerRadius = radius;
-    }
-
-    @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        Canvas canvas = new Canvas(bitmap);
         Path border = new Path();
         border.moveTo(0, 0);
-        int w = bitmap.getWidth();
-        int h = bitmap.getHeight();
-        float bs = mBorderSize / 100.0f * bitmap.getWidth();
-        float r = mBorderCornerRadius / 100.0f * bitmap.getWidth();
+        float bs = getParameters().getBorderSize() / 100.0f * w;
+        float r = getParameters().getBorderRadius() / 100.0f * w;
         border.lineTo(0, h);
         border.lineTo(w, h);
         border.lineTo(w, 0);
@@ -96,9 +55,15 @@
 
         Paint paint = new Paint();
         paint.setAntiAlias(true);
-        paint.setColor(mBorderColor);
+        paint.setColor(getParameters().getColor());
         canvas.drawPath(border, paint);
-        return bitmap;
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+       Canvas canvas = new Canvas(bitmap);
+       applyHelper(canvas, bitmap.getWidth(), bitmap.getHeight());
+       return bitmap;
     }
 
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index cb2bae7..cfbb560 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -16,63 +16,234 @@
 
 package com.android.gallery3d.filtershow.filters;
 
-import android.app.Activity;
 import android.graphics.Bitmap;
-import android.renderscript.Allocation;
-import android.renderscript.RenderScript;
+import android.graphics.BitmapFactory;
+import android.support.v8.renderscript.*;
 import android.util.Log;
+import android.content.res.Resources;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.CachingPipeline;
 
-public class ImageFilterRS extends ImageFilter {
-    private final String LOGTAG = "ImageFilterRS";
+public abstract class ImageFilterRS extends ImageFilter {
+    private static final String LOGTAG = "ImageFilterRS";
+    private boolean DEBUG = false;
+    private int mLastInputWidth = 0;
+    private int mLastInputHeight = 0;
 
-    private static RenderScript mRS = null;
-    protected static Allocation mInPixelsAllocation;
-    protected static Allocation mOutPixelsAllocation;
-    private static android.content.res.Resources mResources = null;
+    public static boolean PERF_LOGGING = false;
 
-    public void prepare(Bitmap bitmap) {
-        mInPixelsAllocation = Allocation.createFromBitmap(mRS, bitmap,
-                Allocation.MipmapControl.MIPMAP_NONE,
-                Allocation.USAGE_SCRIPT);
-        mOutPixelsAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType());
+    private static ScriptC_grey mGreyConvert = null;
+    private static RenderScript mRScache = null;
+
+    private volatile boolean mResourcesLoaded = false;
+
+    protected abstract void createFilter(android.content.res.Resources res,
+            float scaleFactor, int quality);
+
+    protected void createFilter(android.content.res.Resources res,
+    float scaleFactor, int quality, Allocation in) {}
+    protected void bindScriptValues(Allocation in) {}
+
+    protected abstract void runFilter();
+
+    protected void update(Bitmap bitmap) {
+        getOutPixelsAllocation().copyTo(bitmap);
     }
 
-    public void createFilter(android.content.res.Resources res, float scaleFactor,
-            boolean highQuality) {
+    protected RenderScript getRenderScriptContext() {
+        return CachingPipeline.getRenderScriptContext();
     }
 
-    public void runFilter() {
+    protected Allocation getInPixelsAllocation() {
+        CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+        return pipeline.getInPixelsAllocation();
     }
 
-    public void update(Bitmap bitmap) {
-        mOutPixelsAllocation.copyTo(bitmap);
+    protected Allocation getOutPixelsAllocation() {
+        CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+        return pipeline.getOutPixelsAllocation();
     }
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
-        if (bitmap == null) {
+    public void apply(Allocation in, Allocation out) {
+        long startOverAll = System.nanoTime();
+        long startFilter = 0;
+        long endFilter = 0;
+        if (!mResourcesLoaded) {
+            CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+            createFilter(pipeline.getResources(), getEnvironment().getScaleFactor(),
+                    getEnvironment().getQuality(), in);
+            mResourcesLoaded = true;
+        }
+        startFilter = System.nanoTime();
+        bindScriptValues(in);
+        run(in, out);
+        if (PERF_LOGGING) {
+            getRenderScriptContext().finish();
+            endFilter = System.nanoTime();
+            long endOverAll = System.nanoTime();
+            String msg = String.format("%s; image size %dx%d; ", getName(),
+                    in.getType().getX(), in.getType().getY());
+            long timeOverAll = (endOverAll - startOverAll) / 1000;
+            long timeFilter = (endFilter - startFilter) / 1000;
+            msg += String.format("over all %.2f ms (%.2f FPS); ",
+                    timeOverAll / 1000.f, 1000000.f / timeOverAll);
+            msg += String.format("run filter %.2f ms (%.2f FPS)",
+                    timeFilter / 1000.f, 1000000.f / timeFilter);
+            Log.i(LOGTAG, msg);
+        }
+    }
+
+    protected void run(Allocation in, Allocation out) {}
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) {
             return bitmap;
         }
         try {
-            prepare(bitmap);
-            createFilter(mResources, scaleFactor, highQuality);
+            CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+            if (DEBUG) {
+                Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName());
+            }
+            Resources rsc = pipeline.getResources();
+            boolean sizeChanged = false;
+            if (getInPixelsAllocation() != null
+                    && ((getInPixelsAllocation().getType().getX() != mLastInputWidth)
+                    || (getInPixelsAllocation().getType().getY() != mLastInputHeight))) {
+                sizeChanged = true;
+            }
+            if (pipeline.prepareRenderscriptAllocations(bitmap)
+                    || !isResourcesLoaded() || sizeChanged) {
+                freeResources();
+                createFilter(rsc, scaleFactor, quality);
+                setResourcesLoaded(true);
+                mLastInputWidth = getInPixelsAllocation().getType().getX();
+                mLastInputHeight = getInPixelsAllocation().getType().getY();
+            }
+            bindScriptValues();
             runFilter();
             update(bitmap);
+            if (DEBUG) {
+                Log.v(LOGTAG, "DONE apply filter " + getName() + " in pipeline " + pipeline.getName());
+            }
         } catch (android.renderscript.RSIllegalArgumentException e) {
             Log.e(LOGTAG, "Illegal argument? " + e);
         } catch (android.renderscript.RSRuntimeException e) {
             Log.e(LOGTAG, "RS runtime exception ? " + e);
+        } catch (java.lang.OutOfMemoryError e) {
+            // Many of the renderscript filters allocated large (>16Mb resources) in order to apply.
+            System.gc();
+            displayLowMemoryToast();
+            Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
         }
+
         return bitmap;
     }
 
-    public static RenderScript getRenderScriptContext() {
-        return mRS;
+    protected static Allocation convertBitmap(Bitmap bitmap) {
+        return Allocation.createFromBitmap(CachingPipeline.getRenderScriptContext(), bitmap,
+                Allocation.MipmapControl.MIPMAP_NONE,
+                Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
     }
 
-    public static void setRenderScriptContext(Activity context) {
-        mRS = RenderScript.create(context);
-        mResources = context.getResources();
+    private static Allocation convertRGBAtoA(Bitmap bitmap) {
+        RenderScript RS = CachingPipeline.getRenderScriptContext();
+        if (RS != mRScache || mGreyConvert == null) {
+            mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(),
+                                            R.raw.grey);
+            mRScache = RS;
+        }
+
+        Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS));
+
+        Allocation bitmapTemp = convertBitmap(bitmap);
+        if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) {
+            return bitmapTemp;
+        }
+
+        tb_a8.setX(bitmapTemp.getType().getX());
+        tb_a8.setY(bitmapTemp.getType().getY());
+        Allocation bitmapAlloc = Allocation.createTyped(RS, tb_a8.create(),
+                                                        Allocation.MipmapControl.MIPMAP_NONE,
+                                                        Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
+        mGreyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
+        bitmapTemp.destroy();
+        return bitmapAlloc;
     }
 
+    public Allocation loadScaledResourceAlpha(int resource, int inSampleSize) {
+        Resources res = CachingPipeline.getResources();
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+        options.inSampleSize      = inSampleSize;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                res,
+                resource, options);
+        Allocation ret = convertRGBAtoA(bitmap);
+        bitmap.recycle();
+        return ret;
+    }
+
+    public Allocation loadScaledResourceAlpha(int resource, int w, int h, int inSampleSize) {
+        Resources res = CachingPipeline.getResources();
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ALPHA_8;
+        options.inSampleSize      = inSampleSize;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                res,
+                resource, options);
+        Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
+        Allocation ret = convertRGBAtoA(resizeBitmap);
+        resizeBitmap.recycle();
+        bitmap.recycle();
+        return ret;
+    }
+
+    public Allocation loadResourceAlpha(int resource) {
+        return loadScaledResourceAlpha(resource, 1);
+    }
+
+    public Allocation loadResource(int resource) {
+        Resources res = CachingPipeline.getResources();
+        final BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                res,
+                resource, options);
+        Allocation ret = convertBitmap(bitmap);
+        bitmap.recycle();
+        return ret;
+    }
+
+    private boolean isResourcesLoaded() {
+        return mResourcesLoaded;
+    }
+
+    private void setResourcesLoaded(boolean resourcesLoaded) {
+        mResourcesLoaded = resourcesLoaded;
+    }
+
+    /**
+     *  Bitmaps and RS Allocations should be cleared here
+     */
+    abstract protected void resetAllocations();
+
+    /**
+     * RS Script objects (and all other RS objects) should be cleared here
+     */
+    abstract protected void resetScripts();
+
+    /**
+     * Scripts values should be bound here
+     */
+    abstract protected void bindScriptValues();
+
+    public void freeResources() {
+        if (!isResourcesLoaded()) {
+            return;
+        }
+        resetAllocations();
+        setResourcesLoaded(false);
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java
index c77de33..511f9e9 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -17,40 +17,63 @@
 package com.android.gallery3d.filtershow.filters;
 
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+import java.util.Vector;
 
 public class ImageFilterRedEye extends ImageFilter {
-    private static final String TAG = "ImageFilterRedEye";
-
+    private static final String LOGTAG = "ImageFilterRedEye";
+    FilterRedEyeRepresentation mParameters = new FilterRedEyeRepresentation();
 
     public ImageFilterRedEye() {
-        mName = "Redeye";
-
+        mName = "Red Eye";
     }
 
     @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterRedEye filter = (ImageFilterRedEye) super.clone();
-
-        return filter;
+    public FilterRepresentation getDefaultRepresentation() {
+        return new FilterRedEyeRepresentation();
     }
 
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short []matrix);
+    public boolean isNil() {
+        return mParameters.isNil();
+    }
+
+    public Vector<FilterPoint> getCandidates() {
+        return mParameters.getCandidates();
+    }
+
+    public void clear() {
+        mParameters.clearCandidates();
+    }
+
+    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short[] matrix);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterRedEyeRepresentation parameters = (FilterRedEyeRepresentation) representation;
+        mParameters = parameters;
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        float p = mParameter;
-        float value = p;
-        int box = Math.min(w, h);
-        int sizex = Math.min((int)((p+100)*box/400),w/2);
-        int sizey = Math.min((int)((p+100)*box/800),h/2);
+        short[] rect = new short[4];
 
-        short [] rect = new short[]{
-                (short) (w/2-sizex),(short) (w/2-sizey),
-                (short) (2*sizex),(short) (2*sizey)};
-
-        nativeApplyFilter(bitmap, w, h, rect);
+        int size = mParameters.getNumberOfCandidates();
+        Matrix originalToScreen = getOriginalToScreenMatrix(w, h);
+        for (int i = 0; i < size; i++) {
+            RectF r = new RectF(((RedEyeCandidate) (mParameters.getCandidate(i))).mRect);
+            originalToScreen.mapRect(r);
+            if (r.intersect(0, 0, w, h)) {
+                rect[0] = (short) r.left;
+                rect[1] = (short) r.top;
+                rect[2] = (short) r.width();
+                rect[3] = (short) r.height();
+                nativeApplyFilter(bitmap, w, h, rect);
+            }
+        }
         return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
index 1d34591..0febe49 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -16,21 +16,41 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterSaturated extends ImageFilter {
+public class ImageFilterSaturated extends SimpleImageFilter {
 
     public ImageFilterSaturated() {
         mName = "Saturated";
     }
 
+    @Override
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Saturated");
+        representation.setFilterClass(ImageFilterSaturated.class);
+        representation.setTextId(R.string.saturation);
+        representation.setButtonId(R.id.saturationButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        int p = mParameter;
+        int p = getParameters().getValue();
         float value = 1 +  p / 100.0f;
         nativeApplyFilter(bitmap, w, h, value);
         return bitmap;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
index 4e6b848..fd67ee8 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
@@ -16,28 +16,41 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterShadows extends ImageFilter {
+public class ImageFilterShadows extends SimpleImageFilter {
 
     public ImageFilterShadows() {
         mName = "Shadows";
 
     }
 
-    @Override
-    public ImageFilter clone() throws CloneNotSupportedException {
-        ImageFilterShadows filter = (ImageFilterShadows) super.clone();
-        return filter;
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Shadows");
+        representation.setFilterClass(ImageFilterShadows.class);
+        representation.setTextId(R.string.shadow_recovery);
+        representation.setButtonId(R.id.shadowRecoveryButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
     }
 
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float  factor);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        float p = mParameter;
+        float p = getParameters().getValue();
 
         nativeApplyFilter(bitmap, w, h, p);
         return bitmap;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
index a355539..76ae475 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
@@ -23,17 +23,53 @@
     private static final String LOGTAG = "ImageFilterSharpen";
     private ScriptC_convolve3x3 mScript;
 
+    private FilterBasicRepresentation mParameters;
+
     public ImageFilterSharpen() {
         mName = "Sharpen";
     }
 
-    @Override
-    public void createFilter(android.content.res.Resources res, float scaleFactor,
-            boolean highQuality) {
-        int w = mInPixelsAllocation.getType().getX();
-        int h = mInPixelsAllocation.getType().getY();
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterRepresentation representation = new FilterBasicRepresentation("Sharpen", 0, 0, 100);
+        representation.setShowParameterValue(true);
+        representation.setFilterClass(ImageFilterSharpen.class);
+        representation.setTextId(R.string.sharpness);
+        representation.setButtonId(R.id.sharpenButton);
+        representation.setOverlayId(R.drawable.filtershow_button_colors_sharpen);
+        representation.setEditorId(R.id.imageShow);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
 
-        float p1 = mParameter * scaleFactor;
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterBasicRepresentation parameters = (FilterBasicRepresentation) representation;
+        mParameters = parameters;
+    }
+
+    @Override
+    protected void resetAllocations() {
+        // nothing to do
+    }
+
+    @Override
+    protected void resetScripts() {
+        if (mScript != null) {
+            mScript.destroy();
+            mScript = null;
+        }
+    }
+
+    @Override
+    protected void createFilter(android.content.res.Resources res, float scaleFactor,
+            int quality) {
+        if (mScript == null) {
+            mScript = new ScriptC_convolve3x3(getRenderScriptContext(), res, R.raw.convolve3x3);
+        }
+    }
+
+    private void computeKernel() {
+        float scaleFactor = getEnvironment().getScaleFactor();
+        float p1 = mParameters.getValue() * scaleFactor;
         float value = p1 / 100.0f;
         float f[] = new float[9];
         float p = value;
@@ -46,19 +82,26 @@
         f[6] = -p;
         f[7] = -p;
         f[8] = -p;
-        if (mScript == null) {
-            mScript = new ScriptC_convolve3x3(getRenderScriptContext(), res, R.raw.convolve3x3);
-        }
         mScript.set_gCoeffs(f);
+    }
+
+    @Override
+    protected void bindScriptValues() {
+        int w = getInPixelsAllocation().getType().getX();
+        int h = getInPixelsAllocation().getType().getY();
         mScript.set_gWidth(w);
         mScript.set_gHeight(h);
     }
 
     @Override
-    public void runFilter() {
-        mScript.set_gIn(mInPixelsAllocation);
-        mScript.bind_gPixels(mInPixelsAllocation);
-        mScript.forEach_root(mInPixelsAllocation, mOutPixelsAllocation);
+    protected void runFilter() {
+        if (mParameters == null) {
+            return;
+        }
+        computeKernel();
+        mScript.set_gIn(getInPixelsAllocation());
+        mScript.bind_gPixels(getInPixelsAllocation());
+        mScript.forEach_root(getInPixelsAllocation(), getOutPixelsAllocation());
     }
 
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
index 85fcf27..a3bb6f9 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterStraighten.java
@@ -32,6 +32,7 @@
 
     @Override
     public ImageFilter clone() throws CloneNotSupportedException {
+        // FIXME: clone() should not be needed. Remove when we fix geometry.
         ImageFilterStraighten filter = (ImageFilterStraighten) super.clone();
         filter.mRotation = mRotation;
         filter.mZoomFactor = mZoomFactor;
@@ -52,7 +53,12 @@
     }
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public void useRepresentation(FilterRepresentation representation) {
+
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
         // TODO: implement bilinear or bicubic here... for now, just use
         // canvas to do a simple implementation...
         // TODO: and be more memory efficient! (do it in native?)
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
index effd89e..37d5739 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
@@ -18,6 +18,8 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
 import android.graphics.RectF;
 
 import com.adobe.xmp.XMPException;
@@ -28,11 +30,12 @@
 /**
  * An image filter which creates a tiny planet projection.
  */
-public class ImageFilterTinyPlanet extends ImageFilter {
-    private float mAngle = 0;
+public class ImageFilterTinyPlanet extends SimpleImageFilter {
 
-    private static final String TAG = ImageFilterTinyPlanet.class.getSimpleName();
+
+    private static final String LOGTAG = ImageFilterTinyPlanet.class.getSimpleName();
     public static final String GOOGLE_PANO_NAMESPACE = "http://ns.google.com/photos/1.0/panorama/";
+    FilterTinyPlanetRepresentation mParameters = new FilterTinyPlanetRepresentation();
 
     public static final String CROPPED_AREA_IMAGE_WIDTH_PIXELS =
             "CroppedAreaImageWidthPixels";
@@ -48,62 +51,57 @@
             "CroppedAreaTopPixels";
 
     public ImageFilterTinyPlanet() {
-        setFilterType(TYPE_TINYPLANET);
         mName = "TinyPlanet";
-
-        mMinParameter = 10;
-        mMaxParameter = 60;
-        mDefaultParameter = 20;
-        mPreviewParameter = 20;
-        mParameter = 20;
-        mAngle = 0;
     }
 
-    public void setAngle(float angle) {
-        mAngle = angle;
+    @Override
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterTinyPlanetRepresentation parameters = (FilterTinyPlanetRepresentation) representation;
+        mParameters = parameters;
     }
 
-    public float getAngle() {
-        return mAngle;
+    @Override
+    public FilterRepresentation getDefaultRepresentation() {
+        return new FilterTinyPlanetRepresentation();
     }
 
-    public boolean isNil() {
-        // TinyPlanet always has an effect
-        return false;
-    }
 
     native protected void nativeApplyFilter(
             Bitmap bitmapIn, int width, int height, Bitmap bitmapOut, int outSize, float scale,
             float angle);
 
+
     @Override
-    public Bitmap apply(Bitmap bitmapIn, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmapIn, float scaleFactor, int quality) {
         int w = bitmapIn.getWidth();
         int h = bitmapIn.getHeight();
         int outputSize = (int) (w / 2f);
         ImagePreset preset = getImagePreset();
-
+        Bitmap mBitmapOut = null;
         if (preset != null) {
             XMPMeta xmp = preset.getImageLoader().getXmpObject();
             // Do nothing, just use bitmapIn as is if we don't have XMP.
             if(xmp != null) {
-              bitmapIn = applyXmp(bitmapIn, xmp, w);
+                bitmapIn = applyXmp(bitmapIn, xmp, w);
             }
         }
-
-        Bitmap mBitmapOut = null;
+        if (mBitmapOut != null) {
+            if (outputSize != mBitmapOut.getHeight()) {
+                mBitmapOut = null;
+            }
+        }
         while (mBitmapOut == null) {
             try {
-                mBitmapOut = Bitmap.createBitmap(
-                        outputSize, outputSize, Bitmap.Config.ARGB_8888);
+                mBitmapOut = getEnvironment().getBitmap(outputSize, outputSize);
             } catch (java.lang.OutOfMemoryError e) {
                 System.gc();
                 outputSize /= 2;
-                Log.v(TAG, "No memory to create Full Tiny Planet create half");
+                Log.v(LOGTAG, "No memory to create Full Tiny Planet create half");
             }
         }
         nativeApplyFilter(bitmapIn, bitmapIn.getWidth(), bitmapIn.getHeight(), mBitmapOut,
-                outputSize, mParameter / 100f, mAngle);
+                outputSize, mParameters.getZoom() / 100f, mParameters.getAngle());
+
         return mBitmapOut;
     }
 
@@ -120,6 +118,9 @@
             int left = getInt(xmp, CROPPED_AREA_LEFT);
             int top = getInt(xmp, CROPPED_AREA_TOP);
 
+            if (fullPanoWidth == 0 || fullPanoHeight == 0) {
+                return bitmapIn;
+            }
             // Make sure the intermediate image has the similar size to the
             // input.
             Bitmap paddedBitmap = null;
@@ -127,12 +128,11 @@
             while (paddedBitmap == null) {
                 try {
                     paddedBitmap = Bitmap.createBitmap(
-                    (int) (fullPanoWidth * scale), (int) (fullPanoHeight * scale),
-                    Bitmap.Config.ARGB_8888);
+                            (int) (fullPanoWidth * scale), (int) (fullPanoHeight * scale),
+                            Bitmap.Config.ARGB_8888);
                 } catch (java.lang.OutOfMemoryError e) {
                     System.gc();
                     scale /= 2;
-                    Log.v(TAG, "No memory to create Full Tiny Planet create half");
                 }
             }
             Canvas paddedCanvas = new Canvas(paddedBitmap);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
index 34f8b24..ea315d3 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -16,22 +16,40 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+
 import android.graphics.Bitmap;
 
-public class ImageFilterVibrance extends ImageFilter {
+public class ImageFilterVibrance extends SimpleImageFilter {
 
     public ImageFilterVibrance() {
         mName = "Vibrance";
     }
 
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterBasicRepresentation representation =
+                (FilterBasicRepresentation) super.getDefaultRepresentation();
+        representation.setName("Vibrance");
+        representation.setFilterClass(ImageFilterVibrance.class);
+        representation.setTextId(R.string.vibrance);
+        representation.setButtonId(R.id.vibranceButton);
+        representation.setMinimum(-100);
+        representation.setMaximum(100);
+        representation.setDefaultValue(0);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (getParameters() == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        int p = mParameter;
-        float value = p;
+        float value = getParameters().getValue();
         nativeApplyFilter(bitmap, w, h, value);
 
         return bitmap;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
index 7a471e5..e06f544 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -16,25 +16,83 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-public class ImageFilterVignette extends ImageFilter {
+public class ImageFilterVignette extends SimpleImageFilter {
+    private static final String LOGTAG = "ImageFilterVignette";
+    private Bitmap mOverlayBitmap;
 
     public ImageFilterVignette() {
-        setFilterType(TYPE_VIGNETTE);
         mName = "Vignette";
     }
 
-    native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength);
+    @Override
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterVignetteRepresentation representation = new FilterVignetteRepresentation();
+        return representation;
+    }
+
+    native protected void nativeApplyFilter(
+            Bitmap bitmap, int w, int h, int cx, int cy, float radx, float rady, float strength);
+
+    private float calcRadius(float cx, float cy, int w, int h) {
+        float d = cx;
+        if (d < (w - cx)) {
+            d = w - cx;
+        }
+        if (d < cy) {
+            d = cy;
+        }
+        if (d < (h - cy)) {
+            d = h - cy;
+        }
+        return d * d * 2.0f;
+    }
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
+        if (SIMPLE_ICONS && ImagePreset.QUALITY_ICON == quality) {
+            if (mOverlayBitmap == null) {
+                Resources res = getEnvironment().getCachingPipeline().getResources();
+                mOverlayBitmap = IconUtilities.getFXBitmap(res,
+                        R.drawable.filtershow_icon_vignette);
+            }
+            Canvas c = new Canvas(bitmap);
+            int dim = Math.max(bitmap.getWidth(), bitmap.getHeight());
+            Rect r = new Rect(0, 0, dim, dim);
+            c.drawBitmap(mOverlayBitmap, null, r, null);
+            return bitmap;
+        }
+        FilterVignetteRepresentation rep = (FilterVignetteRepresentation) getParameters();
+        if (rep == null) {
+            return bitmap;
+        }
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        float p = mParameter;
-        float value = p / 100.0f;
-        nativeApplyFilter(bitmap, w, h, value);
-
+        float value = rep.getValue() / 100.0f;
+        float cx = w / 2;
+        float cy = h / 2;
+        float r = calcRadius(cx, cy, w, h);
+        float rx = r;
+        float ry = r;
+        if (rep.isCenterSet()) {
+            Matrix m = getOriginalToScreenMatrix(w, h);
+            cx = rep.getCenterX();
+            cy = rep.getCenterY();
+            float[] center = new float[] { cx, cy };
+            m.mapPoints(center);
+            cx = center[0];
+            cy = center[1];
+            rx = m.mapRadius(rep.getRadiusX());
+            ry = m.mapRadius(rep.getRadiusY());
+         }
+        nativeApplyFilter(bitmap, w, h, (int) cx, (int) cy, rx, ry, value);
         return bitmap;
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
index b00b867..c4c293a 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
@@ -16,23 +16,44 @@
 
 package com.android.gallery3d.filtershow.filters;
 
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+
 import android.graphics.Bitmap;
 
 public class ImageFilterWBalance extends ImageFilter {
     private static final String TAG = "ImageFilterWBalance";
 
     public ImageFilterWBalance() {
-        setFilterType(TYPE_WBALANCE);
         mName = "WBalance";
     }
 
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterRepresentation representation = new FilterDirectRepresentation("WBalance");
+        representation.setFilterClass(ImageFilterWBalance.class);
+        representation.setPriority(FilterRepresentation.TYPE_WBALANCE);
+        representation.setTextId(R.string.wbalance);
+        representation.setButtonId(R.id.wbalanceButton);
+        representation.setShowEditingControls(false);
+        representation.setShowParameterValue(false);
+        representation.setEditorId(ImageOnlyEditor.ID);
+        representation.setSupportsPartialRendering(true);
+        return representation;
+    }
+
+    @Override
+    public void useRepresentation(FilterRepresentation representation) {
+
+    }
+
     native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int locX, int locY);
 
     @Override
-    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        nativeApplyFilter(bitmap, w, h, -1,-1);
+        nativeApplyFilter(bitmap, w, h, -1, -1);
         return bitmap;
     }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java
new file mode 100644
index 0000000..a40d4fa
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java
@@ -0,0 +1,50 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.graphics.RectF;
+
+public class RedEyeCandidate implements FilterPoint {
+    RectF mRect = new RectF();
+    RectF mBounds = new RectF();
+
+    public RedEyeCandidate(RedEyeCandidate candidate) {
+        mRect.set(candidate.mRect);
+        mBounds.set(candidate.mBounds);
+    }
+
+    public RedEyeCandidate(RectF rect, RectF bounds) {
+        mRect.set(rect);
+        mBounds.set(bounds);
+    }
+
+    public boolean equals(RedEyeCandidate candidate) {
+        if (candidate.mRect.equals(mRect)
+                && candidate.mBounds.equals(mBounds)) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean intersect(RectF rect) {
+        return mRect.intersect(rect);
+    }
+
+    public RectF getRect() {
+        return mRect;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java b/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java
new file mode 100644
index 0000000..c891d20
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/SimpleImageFilter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+public class SimpleImageFilter extends ImageFilter {
+
+    private FilterBasicRepresentation mParameters;
+
+    public FilterRepresentation getDefaultRepresentation() {
+        FilterRepresentation representation = new FilterBasicRepresentation("Default", 0, 50, 100);
+        representation.setShowParameterValue(true);
+        return representation;
+    }
+
+    public void useRepresentation(FilterRepresentation representation) {
+        FilterBasicRepresentation parameters = (FilterBasicRepresentation) representation;
+        mParameters = parameters;
+    }
+
+    public FilterBasicRepresentation getParameters() {
+        return mParameters;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/SplineMath.java b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
new file mode 100644
index 0000000..5b12d0a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/SplineMath.java
@@ -0,0 +1,166 @@
+package com.android.gallery3d.filtershow.filters;
+
+
+public class SplineMath {
+    double[][] mPoints = new double[6][2];
+    double[] mDerivatives;
+    SplineMath(int n) {
+        mPoints = new double[n][2];
+    }
+
+    public void setPoint(int index, double x, double y) {
+        mPoints[index][0] = x;
+        mPoints[index][1] = y;
+        mDerivatives = null;
+    }
+
+    public float[][] calculatetCurve(int n) {
+        float[][] curve = new float[n][2];
+        double[][] points = new double[mPoints.length][2];
+        for (int i = 0; i < mPoints.length; i++) {
+
+            points[i][0] = mPoints[i][0];
+            points[i][1] = mPoints[i][1];
+
+        }
+        double[] derivatives = solveSystem(points);
+        float start = (float) points[0][0];
+        float end = (float) (points[points.length - 1][0]);
+
+        curve[0][0] = (float) (points[0][0]);
+        curve[0][1] = (float) (points[0][1]);
+        int last = curve.length - 1;
+        curve[last][0] = (float) (points[points.length - 1][0]);
+        curve[last][1] = (float) (points[points.length - 1][1]);
+
+        for (int i = 0; i < curve.length; i++) {
+
+            double[] cur = null;
+            double[] next = null;
+            double x = start + i * (end - start) / (curve.length - 1);
+            int pivot = 0;
+            for (int j = 0; j < points.length - 1; j++) {
+                if (x >= points[j][0] && x <= points[j + 1][0]) {
+                    pivot = j;
+                }
+            }
+            cur = points[pivot];
+            next = points[pivot + 1];
+            if (x <= next[0]) {
+                double x1 = cur[0];
+                double x2 = next[0];
+                double y1 = cur[1];
+                double y2 = next[1];
+
+                // Use the second derivatives to apply the cubic spline
+                // equation:
+                double delta = (x2 - x1);
+                double delta2 = delta * delta;
+                double b = (x - x1) / delta;
+                double a = 1 - b;
+                double ta = a * y1;
+                double tb = b * y2;
+                double tc = (a * a * a - a) * derivatives[pivot];
+                double td = (b * b * b - b) * derivatives[pivot + 1];
+                double y = ta + tb + (delta2 / 6) * (tc + td);
+
+                curve[i][0] = (float) (x);
+                curve[i][1] = (float) (y);
+            } else {
+                curve[i][0] = (float) (next[0]);
+                curve[i][1] = (float) (next[1]);
+            }
+        }
+        return curve;
+    }
+
+    public double getValue(double x) {
+        double[] cur = null;
+        double[] next = null;
+        if (mDerivatives == null)
+            mDerivatives = solveSystem(mPoints);
+        int pivot = 0;
+        for (int j = 0; j < mPoints.length - 1; j++) {
+            pivot = j;
+            if (x <= mPoints[j][0]) {
+                break;
+            }
+        }
+        cur = mPoints[pivot];
+        next = mPoints[pivot + 1];
+        double x1 = cur[0];
+        double x2 = next[0];
+        double y1 = cur[1];
+        double y2 = next[1];
+
+        // Use the second derivatives to apply the cubic spline
+        // equation:
+        double delta = (x2 - x1);
+        double delta2 = delta * delta;
+        double b = (x - x1) / delta;
+        double a = 1 - b;
+        double ta = a * y1;
+        double tb = b * y2;
+        double tc = (a * a * a - a) * mDerivatives[pivot];
+        double td = (b * b * b - b) * mDerivatives[pivot + 1];
+        double y = ta + tb + (delta2 / 6) * (tc + td);
+
+        return y;
+
+    }
+
+    double[] solveSystem(double[][] points) {
+        int n = points.length;
+        double[][] system = new double[n][3];
+        double[] result = new double[n]; // d
+        double[] solution = new double[n]; // returned coefficients
+        system[0][1] = 1;
+        system[n - 1][1] = 1;
+        double d6 = 1.0 / 6.0;
+        double d3 = 1.0 / 3.0;
+
+        // let's create a tridiagonal matrix representing the
+        // system, and apply the TDMA algorithm to solve it
+        // (see http://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
+        for (int i = 1; i < n - 1; i++) {
+            double deltaPrevX = points[i][0] - points[i - 1][0];
+            double deltaX = points[i + 1][0] - points[i - 1][0];
+            double deltaNextX = points[i + 1][0] - points[i][0];
+            double deltaNextY = points[i + 1][1] - points[i][1];
+            double deltaPrevY = points[i][1] - points[i - 1][1];
+            system[i][0] = d6 * deltaPrevX; // a_i
+            system[i][1] = d3 * deltaX; // b_i
+            system[i][2] = d6 * deltaNextX; // c_i
+            result[i] = (deltaNextY / deltaNextX) - (deltaPrevY / deltaPrevX); // d_i
+        }
+
+        // Forward sweep
+        for (int i = 1; i < n; i++) {
+            // m = a_i/b_i-1
+            double m = system[i][0] / system[i - 1][1];
+            // b_i = b_i - m(c_i-1)
+            system[i][1] = system[i][1] - m * system[i - 1][2];
+            // d_i = d_i - m(d_i-1)
+            result[i] = result[i] - m * result[i - 1];
+        }
+
+        // Back substitution
+        solution[n - 1] = result[n - 1] / system[n - 1][1];
+        for (int i = n - 2; i >= 0; --i) {
+            solution[i] = (result[i] - system[i][2] * solution[i + 1]) / system[i][1];
+        }
+        return solution;
+    }
+
+    public static void main(String[] args) {
+        SplineMath s = new SplineMath(10);
+        for (int i = 0; i < 10; i++) {
+            s.setPoint(i, i, i);
+        }
+        float[][] curve = s.calculatetCurve(40);
+
+        for (int j = 0; j < curve.length; j++) {
+            System.out.println(curve[j][0] + "," + curve[j][1]);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/grey.rs b/src/com/android/gallery3d/filtershow/filters/grey.rs
new file mode 100644
index 0000000..e018803
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/grey.rs
@@ -0,0 +1,22 @@
+  /*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.gallery3d.filtershow.filters)
+
+uchar __attribute__((kernel)) RGBAtoA(uchar4 in) {
+    return in.r;
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
new file mode 100644
index 0000000..8ceb375
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/EclipseControl.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.RectF;
+import android.graphics.Shader;
+
+import com.android.gallery3d.R;
+
+public class EclipseControl {
+    private float mCenterX = Float.NaN;
+    private float mCenterY = 0;
+    private float mRadiusX = 200;
+    private float mRadiusY = 300;
+    private static int MIN_TOUCH_DIST = 80;// should be a resource & in dips
+
+    private float[] handlex = new float[9];
+    private float[] handley = new float[9];
+    private int mSliderColor;
+    private int mCenterDotSize = 40;
+    private float mDownX;
+    private float mDownY;
+    private float mDownCenterX;
+    private float mDownCenterY;
+    private float mDownRadiusX;
+    private float mDownRadiusY;
+    private Matrix mScrToImg;
+
+    private boolean mShowReshapeHandles = true;
+    public final static int HAN_CENTER = 0;
+    public final static int HAN_NORTH = 7;
+    public final static int HAN_NE = 8;
+    public final static int HAN_EAST = 1;
+    public final static int HAN_SE = 2;
+    public final static int HAN_SOUTH = 3;
+    public final static int HAN_SW = 4;
+    public final static int HAN_WEST = 5;
+    public final static int HAN_NW = 6;
+
+    public EclipseControl(Context context) {
+        mSliderColor = Color.WHITE;
+    }
+
+    public void setRadius(float x, float y) {
+        mRadiusX = x;
+        mRadiusY = y;
+    }
+
+    public void setCenter(float x, float y) {
+        mCenterX = x;
+        mCenterY = y;
+    }
+
+    public int getCloseHandle(float x, float y) {
+        float min = Float.MAX_VALUE;
+        int handle = -1;
+        for (int i = 0; i < handlex.length; i++) {
+            float dx = handlex[i] - x;
+            float dy = handley[i] - y;
+            float dist = dx * dx + dy * dy;
+            if (dist < min) {
+                min = dist;
+                handle = i;
+            }
+        }
+
+        if (min < MIN_TOUCH_DIST * MIN_TOUCH_DIST) {
+            return handle;
+        }
+        for (int i = 0; i < handlex.length; i++) {
+            float dx = handlex[i] - x;
+            float dy = handley[i] - y;
+            float dist = (float) Math.sqrt(dx * dx + dy * dy);
+        }
+
+        return -1;
+    }
+
+    public void setScrToImageMatrix(Matrix scrToImg) {
+        mScrToImg = scrToImg;
+    }
+
+    public void actionDown(float x, float y, Oval oval) {
+        float[] point = new float[] {
+                x, y };
+        mScrToImg.mapPoints(point);
+        mDownX = point[0];
+        mDownY = point[1];
+        mDownCenterX = oval.getCenterX();
+        mDownCenterY = oval.getCenterY();
+        mDownRadiusX = oval.getRadiusX();
+        mDownRadiusY = oval.getRadiusY();
+    }
+
+    public void actionMove(int handle, float x, float y, Oval oval) {
+        float[] point = new float[] {
+                x, y };
+        mScrToImg.mapPoints(point);
+        x = point[0];
+        y = point[1];
+
+        // Test if the matrix is swapping x and y
+        point[0] = 0;
+        point[1] = 1;
+        mScrToImg.mapVectors(point);
+        boolean swapxy = (point[0] > 0.0f);
+
+        int sign = 1;
+        switch (handle) {
+            case HAN_CENTER:
+                float ctrdx = mDownX - mDownCenterX;
+                float ctrdy = mDownY - mDownCenterY;
+                oval.setCenter(x - ctrdx, y - ctrdy);
+                // setRepresentation(mVignetteRep);
+                break;
+            case HAN_NORTH:
+                sign = -1;
+            case HAN_SOUTH:
+                if (swapxy) {
+                    float raddx = mDownRadiusY - Math.abs(mDownX - mDownCenterY);
+                    oval.setRadiusY(Math.abs(x - oval.getCenterY() + sign * raddx));
+                } else {
+                    float raddy = mDownRadiusY - Math.abs(mDownY - mDownCenterY);
+                    oval.setRadiusY(Math.abs(y - oval.getCenterY() + sign * raddy));
+                }
+                break;
+            case HAN_EAST:
+                sign = -1;
+            case HAN_WEST:
+                if (swapxy) {
+                    float raddy = mDownRadiusX - Math.abs(mDownY - mDownCenterX);
+                    oval.setRadiusX(Math.abs(y - oval.getCenterX() + sign * raddy));
+                } else {
+                    float raddx = mDownRadiusX - Math.abs(mDownX - mDownCenterX);
+                    oval.setRadiusX(Math.abs(x - oval.getCenterX() - sign * raddx));
+                }
+                break;
+            case HAN_SE:
+            case HAN_NE:
+            case HAN_SW:
+            case HAN_NW:
+                float sin45 = (float) Math.sin(45);
+                float dr = (mDownRadiusX + mDownRadiusY) * sin45;
+                float ctr_dx = mDownX - mDownCenterX;
+                float ctr_dy = mDownY - mDownCenterY;
+                float downRad = Math.abs(ctr_dx) + Math.abs(ctr_dy) - dr;
+                float rx = oval.getRadiusX();
+                float ry = oval.getRadiusY();
+                float r = (Math.abs(rx) + Math.abs(ry)) * sin45;
+                float dx = x - oval.getCenterX();
+                float dy = y - oval.getCenterY();
+                float nr = Math.abs(Math.abs(dx) + Math.abs(dy) - downRad);
+                oval.setRadius(rx * nr / r, ry * nr / r);
+
+                break;
+        }
+    }
+
+    public void paintGrayPoint(Canvas canvas, float x, float y) {
+        if (x == Float.NaN) {
+            return;
+        }
+
+        Paint paint = new Paint();
+
+        paint.setStyle(Paint.Style.FILL);
+        paint.setColor(Color.BLUE);
+        int[] colors3 = new int[] {
+                Color.GRAY, Color.LTGRAY, 0x66000000, 0 };
+        RadialGradient g = new RadialGradient(x, y, mCenterDotSize, colors3, new float[] {
+                0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+        paint.setShader(g);
+        canvas.drawCircle(x, y, mCenterDotSize, paint);
+    }
+
+    public void paintPoint(Canvas canvas, float x, float y) {
+        if (x == Float.NaN) {
+            return;
+        }
+
+        Paint paint = new Paint();
+
+        paint.setStyle(Paint.Style.FILL);
+        paint.setColor(Color.BLUE);
+        int[] colors3 = new int[] {
+                mSliderColor, mSliderColor, 0x66000000, 0 };
+        RadialGradient g = new RadialGradient(x, y, mCenterDotSize, colors3, new float[] {
+                0, .3f, .31f, 1 }, Shader.TileMode.CLAMP);
+        paint.setShader(g);
+        canvas.drawCircle(x, y, mCenterDotSize, paint);
+    }
+
+    void paintRadius(Canvas canvas, float cx, float cy, float rx, float ry) {
+        if (cx == Float.NaN) {
+            return;
+        }
+        int mSliderColor = 0xFF33B5E5;
+        Paint paint = new Paint();
+        RectF rect = new RectF(cx - rx, cy - ry, cx + rx, cy + ry);
+        paint.setAntiAlias(true);
+        paint.setStyle(Paint.Style.STROKE);
+        paint.setStrokeWidth(6);
+        paint.setColor(Color.BLACK);
+        paintOvallines(canvas, rect, paint, cx, cy, rx, ry);
+
+        paint.setStrokeWidth(3);
+        paint.setColor(Color.WHITE);
+        paintOvallines(canvas, rect, paint, cx, cy, rx, ry);
+    }
+
+    public void paintOvallines(
+            Canvas canvas, RectF rect, Paint paint, float cx, float cy, float rx, float ry) {
+        canvas.drawOval(rect, paint);
+        float da = 4;
+        float arclen = da + da;
+        if (mShowReshapeHandles) {
+            paint.setStyle(Paint.Style.STROKE);
+
+            for (int i = 0; i < 361; i += 90) {
+                float dx = rx + 10;
+                float dy = ry + 10;
+                rect.left = cx - dx;
+                rect.top = cy - dy;
+                rect.right = cx + dx;
+                rect.bottom = cy + dy;
+                canvas.drawArc(rect, i - da, arclen, false, paint);
+                dx = rx - 10;
+                dy = ry - 10;
+                rect.left = cx - dx;
+                rect.top = cy - dy;
+                rect.right = cx + dx;
+                rect.bottom = cy + dy;
+                canvas.drawArc(rect, i - da, arclen, false, paint);
+            }
+        }
+        da *= 2;
+        paint.setStyle(Paint.Style.FILL);
+
+        for (int i = 45; i < 361; i += 90) {
+            double angle = Math.PI * i / 180.;
+            float x = cx + (float) (rx * Math.cos(angle));
+            float y = cy + (float) (ry * Math.sin(angle));
+            canvas.drawRect(x - da, y - da, x + da, y + da, paint);
+        }
+        paint.setStyle(Paint.Style.STROKE);
+        rect.left = cx - rx;
+        rect.top = cy - ry;
+        rect.right = cx + rx;
+        rect.bottom = cy + ry;
+    }
+
+    public void fillHandles(Canvas canvas, float cx, float cy, float rx, float ry) {
+        handlex[0] = cx;
+        handley[0] = cy;
+        int k = 1;
+
+        for (int i = 0; i < 360; i += 45) {
+            double angle = Math.PI * i / 180.;
+
+            float x = cx + (float) (rx * Math.cos(angle));
+            float y = cy + (float) (ry * Math.sin(angle));
+            handlex[k] = x;
+            handley[k] = y;
+
+            k++;
+        }
+    }
+
+    public void draw(Canvas canvas) {
+        paintRadius(canvas, mCenterX, mCenterY, mRadiusX, mRadiusY);
+        fillHandles(canvas, mCenterX, mCenterY, mRadiusX, mRadiusY);
+        paintPoint(canvas, mCenterX, mCenterY);
+    }
+
+    public boolean isUndefined() {
+        return Float.isNaN(mCenterX);
+    }
+
+    public void setShowReshapeHandles(boolean showReshapeHandles) {
+        this.mShowReshapeHandles = showReshapeHandles;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryListener.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryListener.java
new file mode 100644
index 0000000..549c2e7
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.imageshow;
+
+public interface GeometryListener {
+    public void geometryChanged();
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
index 55f7918..568dadf 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java
@@ -26,11 +26,37 @@
         return Math.max(Math.min(i, high), low);
     }
 
-    protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) {
-        float x1 = l1[0];
-        float x2 = l2[0];
-        float y1 = l1[1];
-        float y2 = l2[1];
+    public static float[] lineIntersect(float[] line1, float[] line2) {
+        float a0 = line1[0];
+        float a1 = line1[1];
+        float b0 = line1[2];
+        float b1 = line1[3];
+        float c0 = line2[0];
+        float c1 = line2[1];
+        float d0 = line2[2];
+        float d1 = line2[3];
+        float t0 = a0 - b0;
+        float t1 = a1 - b1;
+        float t2 = b0 - d0;
+        float t3 = d1 - b1;
+        float t4 = c0 - d0;
+        float t5 = c1 - d1;
+
+        float denom = t1 * t4 - t0 * t5;
+        if (denom == 0)
+            return null;
+        float u = (t3 * t4 + t5 * t2) / denom;
+        float[] intersect = {
+                b0 + u * t0, b1 + u * t1
+        };
+        return intersect;
+    }
+
+    public static float[] shortestVectorFromPointToLine(float[] point, float[] line) {
+        float x1 = line[0];
+        float x2 = line[2];
+        float y1 = line[1];
+        float y2 = line[3];
         float xdelt = x2 - x1;
         float ydelt = y2 - y1;
         if (xdelt == 0 && ydelt == 0)
@@ -40,67 +66,75 @@
         float[] ret = {
                 (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1))
         };
-        float [] vec = {ret[0] - point[0], ret[1] - point[1] };
+        float[] vec = {
+                ret[0] - point[0], ret[1] - point[1]
+        };
         return vec;
     }
 
     // A . B
-    public static float dotProduct(float[] a, float[] b){
+    public static float dotProduct(float[] a, float[] b) {
         return a[0] * b[0] + a[1] * b[1];
     }
 
-    public static float[] normalize(float[] a){
+    public static float[] normalize(float[] a) {
         float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
-        float[] b = { a[0] / length, a[1] / length };
+        float[] b = {
+                a[0] / length, a[1] / length
+        };
         return b;
     }
 
     // A onto B
-    public static float scalarProjection(float[] a, float[] b){
+    public static float scalarProjection(float[] a, float[] b) {
         float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]);
         return dotProduct(a, b) / length;
     }
 
-    public static float[] getVectorFromPoints(float [] point1, float [] point2){
-        float [] p = { point2[0] - point1[0], point2[1] - point1[1] };
+    public static float[] getVectorFromPoints(float[] point1, float[] point2) {
+        float[] p = {
+                point2[0] - point1[0], point2[1] - point1[1]
+        };
         return p;
     }
 
-    public static float[] getUnitVectorFromPoints(float [] point1, float [] point2){
-        float [] p = { point2[0] - point1[0], point2[1] - point1[1] };
+    public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) {
+        float[] p = {
+                point2[0] - point1[0], point2[1] - point1[1]
+        };
         float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]);
         p[0] = p[0] / length;
         p[1] = p[1] / length;
         return p;
     }
 
-    public static RectF scaleRect(RectF r, float scale){
+    public static RectF scaleRect(RectF r, float scale) {
         return new RectF(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale);
     }
 
     // A - B
-    public static float[] vectorSubtract(float [] a, float [] b){
+    public static float[] vectorSubtract(float[] a, float[] b) {
         int len = a.length;
         if (len != b.length)
             return null;
-        float [] ret = new float[len];
-        for (int i = 0; i < len; i++){
+        float[] ret = new float[len];
+        for (int i = 0; i < len; i++) {
             ret[i] = a[i] - b[i];
         }
         return ret;
     }
 
-    public static float vectorLength(float [] a){
+    public static float vectorLength(float[] a) {
         return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]);
     }
 
     public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) {
         if (oldHeight == 0 || oldWidth == 0)
             return 1;
-        return Math.min(newWidth / oldWidth , newHeight / oldHeight);
+        return Math.min(newWidth / oldWidth, newHeight / oldHeight);
     }
 
-    public static Rect roundNearest(RectF r){
+    public static Rect roundNearest(RectF r) {
         Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right),
                 Math.round(r.bottom));
         return q;
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index dffdc24..e5820a8 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -22,12 +22,15 @@
 import android.graphics.RectF;
 
 import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
 import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
 
-public class GeometryMetadata {
-    // Applied in order: rotate, crop, scale.
-    // Do not scale saved image (presumably?).
-    private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry();
+public class GeometryMetadata extends FilterRepresentation {
     private static final String LOGTAG = "GeometryMetadata";
     private float mScaleFactor = 1.0f;
     private float mRotation = 0;
@@ -36,16 +39,49 @@
     private final RectF mPhotoBounds = new RectF();
     private FLIP mFlip = FLIP.NONE;
 
-    private RectF mBounds = new RectF();
-
     public enum FLIP {
         NONE, VERTICAL, HORIZONTAL, BOTH
     }
 
+    // Output format data from intent extras
+    private boolean mUseCropExtras = false;
+    private CropExtras mCropExtras = null;
+    public void setUseCropExtrasFlag(boolean f){
+        mUseCropExtras = f;
+    }
+
+    public boolean getUseCropExtrasFlag(){
+        return mUseCropExtras;
+    }
+
+    public void setCropExtras(CropExtras e){
+        mCropExtras = e;
+    }
+
+    public CropExtras getCropExtras(){
+        return mCropExtras;
+    }
+
     public GeometryMetadata() {
+        super("GeometryMetadata");
+        setFilterClass(ImageFilterGeometry.class);
+        setEditorId(EditorCrop.ID);
+        setTextId(0);
+        setShowParameterValue(true);
+    }
+
+    @Override
+    public int[] getEditorIds() {
+        return new int[] {
+                EditorCrop.ID,
+                EditorStraighten.ID,
+                EditorRotate.ID,
+                EditorFlip.ID
+        };
     }
 
     public GeometryMetadata(GeometryMetadata g) {
+        super("GeometryMetadata");
         set(g);
     }
 
@@ -70,15 +106,6 @@
         return false;
     }
 
-    public Bitmap apply(Bitmap original, float scaleFactor, boolean highQuality) {
-        if (!hasModifications()) {
-            return original;
-        }
-        mImageFilter.setGeometryMetadata(this);
-        Bitmap m = mImageFilter.apply(original, scaleFactor, highQuality);
-        return m;
-    }
-
     public void set(GeometryMetadata g) {
         mScaleFactor = g.mScaleFactor;
         mRotation = g.mRotation;
@@ -86,7 +113,11 @@
         mCropBounds.set(g.mCropBounds);
         mPhotoBounds.set(g.mPhotoBounds);
         mFlip = g.mFlip;
-        mBounds = g.mBounds;
+
+        mUseCropExtras = g.mUseCropExtras;
+        if (g.mCropExtras != null){
+            mCropExtras = new CropExtras(g.mCropExtras);
+        }
     }
 
     public float getScaleFactor() {
@@ -109,8 +140,21 @@
         float scale = 1.0f;
         scale = GeometryMath.scale(mPhotoBounds.width(), mPhotoBounds.height(), bitmap.getWidth(),
                 bitmap.getHeight());
-        return new RectF(mCropBounds.left * scale, mCropBounds.top * scale,
+        RectF croppedRegion = new RectF(mCropBounds.left * scale, mCropBounds.top * scale,
                 mCropBounds.right * scale, mCropBounds.bottom * scale);
+
+        // If no crop has been applied, make sure to use the exact size values.
+        // Multiplying using scale will introduce rounding errors that modify
+        // even un-cropped images.
+        if (mCropBounds.left == 0 && mCropBounds.right == mPhotoBounds.right) {
+            croppedRegion.left = 0;
+            croppedRegion.right = bitmap.getWidth();
+        }
+        if (mCropBounds.top == 0 && mCropBounds.bottom == mPhotoBounds.bottom) {
+            croppedRegion.top = 0;
+            croppedRegion.bottom = bitmap.getHeight();
+        }
+        return croppedRegion;
     }
 
     public FLIP getFlipType() {
@@ -149,19 +193,27 @@
         return mPhotoBounds.contains(cropBounds);
     }
 
+    private boolean compareRectF(RectF a, RectF b) {
+        return ((int) a.left == (int) b.left)
+                && ((int) a.right == (int) b.right)
+                && ((int) a.top == (int) b.top)
+                && ((int) a.bottom == (int) b.bottom);
+    }
+
     @Override
-    public boolean equals(Object o) {
+    public boolean equals(FilterRepresentation o) {
         if (this == o)
             return true;
-        if (o == null || getClass() != o.getClass())
+        if (o == null || !(o instanceof GeometryMetadata))
             return false;
 
         GeometryMetadata d = (GeometryMetadata) o;
-        return (mScaleFactor == d.mScaleFactor &&
-                mRotation == d.mRotation &&
-                mStraightenRotation == d.mStraightenRotation &&
-                mFlip == d.mFlip &&
-                mCropBounds.equals(d.mCropBounds) && mPhotoBounds.equals(d.mPhotoBounds));
+        return (mScaleFactor == d.mScaleFactor
+                && mRotation == d.mRotation
+                && mStraightenRotation == d.mStraightenRotation
+                && mFlip == d.mFlip
+                && compareRectF(mCropBounds, d.mCropBounds)
+                && compareRectF(mPhotoBounds, d.mPhotoBounds));
     }
 
     @Override
@@ -184,48 +236,16 @@
                 + ",photoRect=" + mPhotoBounds.toShortString() + "]";
     }
 
-    // TODO: refactor away
-    protected static Matrix getHorizontalMatrix(float width) {
-        Matrix flipHorizontalMatrix = new Matrix();
-        flipHorizontalMatrix.setScale(-1, 1);
-        flipHorizontalMatrix.postTranslate(width, 0);
-        return flipHorizontalMatrix;
-    }
-
     protected static void concatHorizontalMatrix(Matrix m, float width) {
         m.postScale(-1, 1);
         m.postTranslate(width, 0);
     }
 
-    // TODO: refactor away
-    protected static Matrix getVerticalMatrix(float height) {
-        Matrix flipVerticalMatrix = new Matrix();
-        flipVerticalMatrix.setScale(1, -1);
-        flipVerticalMatrix.postTranslate(0, height);
-        return flipVerticalMatrix;
-    }
-
     protected static void concatVerticalMatrix(Matrix m, float height) {
         m.postScale(1, -1);
         m.postTranslate(0, height);
     }
 
-    // TODO: refactor away
-    public static Matrix getFlipMatrix(float width, float height, FLIP type) {
-        if (type == FLIP.HORIZONTAL) {
-            return getHorizontalMatrix(width);
-        } else if (type == FLIP.VERTICAL) {
-            return getVerticalMatrix(height);
-        } else if (type == FLIP.BOTH) {
-            Matrix flipper = getVerticalMatrix(height);
-            flipper.postConcat(getHorizontalMatrix(width));
-            return flipper;
-        } else {
-            Matrix m = new Matrix();
-            m.reset(); // identity
-            return m;
-        }
-    }
 
     public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) {
         if (type == FLIP.HORIZONTAL) {
@@ -331,46 +351,10 @@
         return m1;
     }
 
-    // TODO: refactor away
-    public Matrix getFlipMatrix(float width, float height) {
-        FLIP type = getFlipType();
-        return getFlipMatrix(width, height, type);
-    }
-
     public boolean hasSwitchedWidthHeight() {
         return (((int) (mRotation / 90)) % 2) != 0;
     }
 
-    // TODO: refactor away
-    public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy,
-            float rotation) {
-        float dx0 = width / 2;
-        float dy0 = height / 2;
-        Matrix m = getFlipMatrix(width, height);
-        m.postTranslate(-dx0, -dy0);
-        m.postRotate(rotation);
-        m.postScale(scaling, scaling);
-        m.postTranslate(dx, dy);
-        return m;
-    }
-
-    // TODO: refactor away
-    public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy,
-            boolean onlyRotate) {
-        float rot = mRotation;
-        if (!onlyRotate) {
-            rot += mStraightenRotation;
-        }
-        return buildGeometryMatrix(width, height, scaling, dx, dy, rot);
-    }
-
-    // TODO: refactor away
-    public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) {
-        float w = mPhotoBounds.width();
-        float h = mPhotoBounds.height();
-        return buildGeometryMatrix(w, h, scaling, dx, dy, false);
-    }
-
     public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation,
             float straighten, FLIP type) {
         Matrix m = new Matrix();
@@ -404,11 +388,22 @@
     public Matrix buildTotalXform(float bmWidth, float bmHeight, float[] displayCenter) {
         RectF rp = getPhotoBounds();
         RectF rc = getPreviewCropBounds();
-
         float scale = GeometryMath.scale(rp.width(), rp.height(), bmWidth, bmHeight);
         RectF scaledCrop = GeometryMath.scaleRect(rc, scale);
         RectF scaledPhoto = GeometryMath.scaleRect(rp, scale);
 
+        // If no crop has been applied, make sure to use the exact size values.
+        // Multiplying using scale will introduce rounding errors that modify
+        // even un-cropped images.
+        if (rc.left == 0 && rc.right == rp.right) {
+            scaledCrop.left = scaledPhoto.left = 0;
+            scaledCrop.right = scaledPhoto.right = bmWidth;
+        }
+        if (rc.top == 0 && rc.bottom == rp.bottom) {
+            scaledCrop.top = scaledPhoto.top = 0;
+            scaledCrop.bottom = scaledPhoto.bottom = bmHeight;
+        }
+
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
                 getRotation(), getStraightenRotation(),
                 getFlipType(), displayCenter);
@@ -484,4 +479,17 @@
         m.preRotate(-straighten, photo.centerX(), photo.centerY());
         return m;
     }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        GeometryMetadata data = (GeometryMetadata) a;
+        set(data);
+    }
+
+    @Override
+    public FilterRepresentation clone() throws CloneNotSupportedException {
+        GeometryMetadata representation = (GeometryMetadata) super.clone();
+        representation.useParametersFrom(this);
+        return representation;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
deleted file mode 100644
index 0977d7d..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageBorder.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-
-public class ImageBorder extends ImageSlave {
-    Paint gPaint = new Paint();
-
-    public ImageBorder(Context context) {
-        super(context);
-    }
-
-    public ImageBorder(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public boolean showTitle() {
-        return false;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        /*
-         * gPaint.setAntiAlias(true); gPaint.setFilterBitmap(true);
-         * gPaint.setDither(true); // Bitmap bmp =
-         * BitmapFactory.decodeResource(getResources(), //
-         * R.drawable.border_scratch); // canvas.drawBitmap(bmp, new Rect(0, 0,
-         * bmp.getWidth(), // bmp.getHeight()), mImageBounds, new Paint()); if
-         * (mImageBounds != null) { NinePatchDrawable npd = (NinePatchDrawable)
-         * getContext() .getResources().getDrawable(R.drawable.border_scratch2);
-         * npd.setBounds(mImageBounds); npd.draw(canvas); }
-         */
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
index a352a16..f6271c6 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -26,25 +26,36 @@
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
-import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.crop.BoundedRect;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.crop.CropMath;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+import com.android.gallery3d.filtershow.ui.FramedTextButton;
 
 public class ImageCrop extends ImageGeometry {
     private static final boolean LOGV = false;
+
+    // Sides
     private static final int MOVE_LEFT = 1;
     private static final int MOVE_TOP = 2;
     private static final int MOVE_RIGHT = 4;
     private static final int MOVE_BOTTOM = 8;
     private static final int MOVE_BLOCK = 16;
 
-    //Corners
+    // Corners
     private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
     private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
     private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
     private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
 
-    private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f;
+    private static int mMinSideSize = 100;
     private static int mTouchTolerance = 45;
 
     private boolean mFirstDraw = true;
@@ -53,23 +64,34 @@
     private boolean mFixAspectRatio = false;
 
     private float mLastRot = 0;
-    private final Paint borderPaint;
 
+    private BoundedRect mBounded = null;
     private int movingEdges;
     private final Drawable cropIndicator;
     private final int indicatorSize;
     private final int mBorderColor = Color.argb(128, 255, 255, 255);
 
+    // Offset between crop center and photo center
+    private float[] mOffset = {
+            0, 0
+    };
+    private CropExtras mCropExtras = null;
+    private boolean mDoingCropIntentAction = false;
+
     private static final String LOGTAG = "ImageCrop";
 
     private String mAspect = "";
-    private int mAspectTextSize = 24;
+    private static int mAspectTextSize = 24;
 
-    public void setAspectTextSize(int textSize){
+    private boolean mFixedAspect = false;
+
+    private EditorCrop mEditorCrop;
+
+    public static void setAspectTextSize(int textSize) {
         mAspectTextSize = textSize;
     }
 
-    public void setAspectString(String a){
+    public void setAspectString(String a) {
         mAspect = a;
     }
 
@@ -80,10 +102,6 @@
         Resources resources = context.getResources();
         cropIndicator = resources.getDrawable(R.drawable.camera_crop);
         indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
-        borderPaint = new Paint();
-        borderPaint.setStyle(Paint.Style.STROKE);
-        borderPaint.setColor(mBorderColor);
-        borderPaint.setStrokeWidth(2f);
     }
 
     public ImageCrop(Context context, AttributeSet attrs) {
@@ -91,10 +109,6 @@
         Resources resources = context.getResources();
         cropIndicator = resources.getDrawable(R.drawable.camera_crop);
         indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size);
-        borderPaint = new Paint();
-        borderPaint.setStyle(Paint.Style.STROKE);
-        borderPaint.setColor(mBorderColor);
-        borderPaint.setStrokeWidth(2f);
     }
 
     @Override
@@ -102,78 +116,38 @@
         return getContext().getString(R.string.crop);
     }
 
-    private void swapAspect(){
+    private void swapAspect() {
+        if (mDoingCropIntentAction) {
+            return;
+        }
         float temp = mAspectWidth;
         mAspectWidth = mAspectHeight;
         mAspectHeight = temp;
     }
 
-    public static void setTouchTolerance(int tolerance){
+    /**
+     * Set tolerance for crop marker selection (in pixels)
+     */
+    public static void setTouchTolerance(int tolerance) {
         mTouchTolerance = tolerance;
     }
 
-    private boolean switchCropBounds(int moving_corner, RectF dst) {
-        RectF crop = getCropBoundsDisplayed();
-        float dx1 = 0;
-        float dy1 = 0;
-        float dx2 = 0;
-        float dy2 = 0;
-        if ((moving_corner & MOVE_RIGHT) != 0) {
-            dx1 = mCurrentX - crop.right;
-        } else if ((moving_corner & MOVE_LEFT) != 0) {
-            dx1 = mCurrentX - crop.left;
-        }
-        if ((moving_corner & MOVE_BOTTOM) != 0) {
-            dy1 = mCurrentY - crop.bottom;
-        } else if ((moving_corner & MOVE_TOP) != 0) {
-            dy1 = mCurrentY - crop.top;
-        }
-        RectF newCrop = null;
-        //Fix opposite corner in place and move sides
-        if (moving_corner == BOTTOM_RIGHT) {
-            newCrop = new RectF(crop.left, crop.top, crop.left + crop.height(), crop.top
-                    + crop.width());
-        } else if (moving_corner == BOTTOM_LEFT) {
-            newCrop = new RectF(crop.right - crop.height(), crop.top, crop.right, crop.top
-                    + crop.width());
-        } else if (moving_corner == TOP_LEFT) {
-            newCrop = new RectF(crop.right - crop.height(), crop.bottom - crop.width(),
-                    crop.right, crop.bottom);
-        } else if (moving_corner == TOP_RIGHT) {
-            newCrop = new RectF(crop.left, crop.bottom - crop.width(), crop.left
-                    + crop.height(), crop.bottom);
-        }
-        if ((moving_corner & MOVE_RIGHT) != 0) {
-            dx2 = mCurrentX - newCrop.right;
-        } else if ((moving_corner & MOVE_LEFT) != 0) {
-            dx2 = mCurrentX - newCrop.left;
-        }
-        if ((moving_corner & MOVE_BOTTOM) != 0) {
-            dy2 = mCurrentY - newCrop.bottom;
-        } else if ((moving_corner & MOVE_TOP) != 0) {
-            dy2 = mCurrentY - newCrop.top;
-        }
-        if (Math.sqrt(dx1*dx1 + dy1*dy1) > Math.sqrt(dx2*dx2 + dy2*dy2)){
-             Matrix m = getCropBoundDisplayMatrix();
-             Matrix m0 = new Matrix();
-             if (!m.invert(m0)){
-                 if (LOGV)
-                     Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX");
-                 return false;
-             }
-             if (!m0.mapRect(newCrop)){
-                 if (LOGV)
-                     Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE");
-                 return false;
-             }
-             swapAspect();
-             dst.set(newCrop);
-             return true;
-        }
-        return false;
+    /**
+     * Set minimum side length for crop box (in pixels)
+     */
+    public static void setMinCropSize(int minHeightWidth) {
+        mMinSideSize = minHeightWidth;
     }
 
-    public void apply(float w, float h){
+    public void setExtras(CropExtras e) {
+        mCropExtras = e;
+    }
+
+    public void setCropActionFlag(boolean f) {
+        mDoingCropIntentAction = f;
+    }
+
+    public void apply(float w, float h) {
         mFixAspectRatio = true;
         mAspectWidth = w;
         mAspectHeight = h;
@@ -201,6 +175,8 @@
 
     public void applyClear() {
         mFixAspectRatio = false;
+        mAspectWidth = 1;
+        mAspectHeight = 1;
         setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
                 getLocalStraighten()));
         cropSetup();
@@ -208,188 +184,139 @@
         invalidate();
     }
 
-    private float getScaledMinWidthHeight() {
-        RectF disp = new RectF(0, 0, getWidth(), getHeight());
-        float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT
-                / computeScale(getWidth(), getHeight());
-        return scaled;
+    public void clear() {
+        if (mCropExtras != null) {
+            int x = mCropExtras.getAspectX();
+            int y = mCropExtras.getAspectY();
+            if (mDoingCropIntentAction && x > 0 && y > 0) {
+                apply(x, y);
+            }
+        } else {
+            applyClear();
+        }
     }
 
-    protected Matrix getCropRotationMatrix(float rotation, RectF localImage) {
-        Matrix m = getLocalGeoFlipMatrix(localImage.width(), localImage.height());
-        m.postRotate(rotation, localImage.centerX(), localImage.centerY());
-        if (!m.rectStaysRect()) {
-            return null;
-        }
+    private Matrix getPhotoBoundDisplayedMatrix() {
+        float[] displayCenter = new float[2];
+        RectF scaledCrop = new RectF();
+        RectF scaledPhoto = new RectF();
+        float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
+        Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
+                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+        m.preScale(scale, scale);
         return m;
     }
 
-    protected Matrix getCropBoundDisplayMatrix(){
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-        if (m == null) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE");
-            m = new Matrix();
-        }
-        float zoom = computeScale(getWidth(), getHeight());
-        m.postTranslate(mXOffset, mYOffset);
-        m.postScale(zoom, zoom, mCenterX, mCenterY);
-        return m;
+    private Matrix getCropBoundDisplayedMatrix() {
+        float[] displayCenter = new float[2];
+        RectF scaledCrop = new RectF();
+        RectF scaledPhoto = new RectF();
+        float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
+        Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
+                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+        m1.preScale(scale, scale);
+        return m1;
     }
 
-    protected RectF getCropBoundsDisplayed() {
-        RectF bounds = getLocalCropBounds();
-        RectF crop = new RectF(bounds);
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-
-        if (m == null) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE");
-            m = new Matrix();
-        } else {
-            m.mapRect(crop);
-        }
-        m = new Matrix();
-        float zoom = computeScale(getWidth(), getHeight());
-        m.setScale(zoom, zoom, mCenterX, mCenterY);
-        m.preTranslate(mXOffset, mYOffset);
-        m.mapRect(crop);
-        return crop;
-    }
-
-    private RectF getRotatedCropBounds() {
-        RectF bounds = getLocalCropBounds();
-        RectF crop = new RectF(bounds);
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-
-        if (m == null) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE");
-            return null;
-        } else {
-            m.mapRect(crop);
-        }
-        return crop;
-    }
-
-    private RectF getUnrotatedCropBounds(RectF cropBounds) {
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-
-        if (m == null) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO GET ROTATION MATRIX");
-            return null;
-        }
-        Matrix m0 = new Matrix();
-        if (!m.invert(m0)) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX");
-            return null;
-        }
-        RectF crop = new RectF(cropBounds);
-        if (!m0.mapRect(crop)) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS");
-            return null;
-        }
-        return crop;
-    }
-
-    private RectF getRotatedStraightenBounds() {
-        RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
-                getLocalStraighten());
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-
-        if (m == null) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO MAP STRAIGHTEN BOUNDS TO RECTANGLE");
-            return null;
-        } else {
-            m.mapRect(straightenBounds);
-        }
-        return straightenBounds;
+    /**
+     * Takes the rotated corners of a rectangle and returns the angle; sets
+     * unrotated to be the unrotated version of the rectangle.
+     */
+    private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
+        float dy = rotatedRect[1] - rotatedRect[3];
+        float dx = rotatedRect[0] - rotatedRect[2];
+        float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI);
+        Matrix m = new Matrix();
+        m.setRotate(-angle, center[0], center[1]);
+        float[] unrotatedRect = new float[rotatedRect.length];
+        m.mapPoints(unrotatedRect, rotatedRect);
+        unrotated.set(CropMath.trapToRect(unrotatedRect));
+        return angle;
     }
 
     /**
      * Sets cropped bounds; modifies the bounds if it's smaller than the allowed
      * dimensions.
      */
-    public void setCropBounds(RectF bounds) {
-        // Avoid cropping smaller than minimum width or height.
+    public boolean setCropBounds(RectF bounds) {
         RectF cbounds = new RectF(bounds);
-        float minWidthHeight = getScaledMinWidthHeight();
-        float aw = mAspectWidth;
-        float ah = mAspectHeight;
-        if (mFixAspectRatio) {
-            minWidthHeight /= aw * ah;
-            int r = (int) (getLocalRotation() / 90);
-            if (r % 2 != 0) {
-                float temp = aw;
-                aw = ah;
-                ah = temp;
-            }
-        }
-
+        Matrix mc = getCropBoundDisplayedMatrix();
+        Matrix mcInv = new Matrix();
+        mc.invert(mcInv);
+        mcInv.mapRect(cbounds);
+        // Avoid cropping smaller than minimum
         float newWidth = cbounds.width();
         float newHeight = cbounds.height();
-        if (mFixAspectRatio) {
-            if (newWidth < (minWidthHeight * aw) || newHeight < (minWidthHeight * ah)) {
-                newWidth = minWidthHeight * aw;
-                newHeight = minWidthHeight * ah;
-            }
-        } else {
-            if (newWidth < minWidthHeight) {
-                newWidth = minWidthHeight;
-            }
-            if (newHeight < minWidthHeight) {
-                newHeight = minWidthHeight;
-            }
-        }
+        float scale = getTransformState(null, null, null);
+        float minWidthHeight = mMinSideSize / scale;
         RectF pbounds = getLocalPhotoBounds();
-        if (pbounds.width() < minWidthHeight) {
-            newWidth = pbounds.width();
-        }
-        if (pbounds.height() < minWidthHeight) {
-            newHeight = pbounds.height();
+
+        // if photo is smaller than minimum, refuse to set crop bounds
+        if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) {
+            return false;
         }
 
-        cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight);
-        RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
-                getLocalStraighten());
-        cbounds.intersect(straightenBounds);
-
-        if (mFixAspectRatio) {
-            fixAspectRatio(cbounds, aw, ah);
+        // if incoming crop is smaller than minimum, refuse to set crop bounds
+        if (newWidth < minWidthHeight || newHeight < minWidthHeight) {
+            return false;
         }
+
+        float newX = bounds.centerX() - (getWidth() / 2f);
+        float newY = bounds.centerY() - (getHeight() / 2f);
+        mOffset[0] = newX;
+        mOffset[1] = newY;
+
         setLocalCropBounds(cbounds);
         invalidate();
+        return true;
+    }
+
+    private BoundedRect getBoundedCrop(RectF crop) {
+        RectF photo = getLocalPhotoBounds();
+        Matrix mp = getPhotoBoundDisplayedMatrix();
+        float[] photoCorners = CropMath.getCornersFromRect(photo);
+        float[] photoCenter = {
+                photo.centerX(), photo.centerY()
+        };
+        mp.mapPoints(photoCorners);
+        mp.mapPoints(photoCenter);
+        RectF scaledPhoto = new RectF();
+        float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto);
+        return new BoundedRect(angle, scaledPhoto, crop);
     }
 
     private void detectMovingEdges(float x, float y) {
-        RectF cropped = getCropBoundsDisplayed();
+        Matrix m = getCropBoundDisplayedMatrix();
+        RectF cropped = getLocalCropBounds();
+        m.mapRect(cropped);
+        mBounded = getBoundedCrop(cropped);
         movingEdges = 0;
 
-        // Check left or right.
         float left = Math.abs(x - cropped.left);
         float right = Math.abs(x - cropped.right);
-        if ((left <= mTouchTolerance) && (left < right)) {
+        float top = Math.abs(y - cropped.top);
+        float bottom = Math.abs(y - cropped.bottom);
+
+        // Check left or right.
+        if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+                && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
             movingEdges |= MOVE_LEFT;
         }
-        else if (right <= mTouchTolerance) {
+        else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
+                && ((y - mTouchTolerance) <= cropped.bottom)) {
             movingEdges |= MOVE_RIGHT;
         }
 
         // Check top or bottom.
-        float top = Math.abs(y - cropped.top);
-        float bottom = Math.abs(y - cropped.bottom);
-        if ((top <= mTouchTolerance) & (top < bottom)) {
+        if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+                && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
             movingEdges |= MOVE_TOP;
         }
-        else if (bottom <= mTouchTolerance) {
+        else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
+                && ((x - mTouchTolerance) <= cropped.right)) {
             movingEdges |= MOVE_BOTTOM;
         }
-        // Check inside block.
-        if (cropped.contains(x, y) && (movingEdges == 0)) {
+        if (movingEdges == 0) {
             movingEdges = MOVE_BLOCK;
         }
         if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) {
@@ -398,7 +325,7 @@
         invalidate();
     }
 
-    private int fixEdgeToCorner(int moving_edges){
+    private int fixEdgeToCorner(int moving_edges) {
         if (moving_edges == MOVE_LEFT) {
             moving_edges |= MOVE_TOP;
         }
@@ -414,9 +341,9 @@
         return moving_edges;
     }
 
-    private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy){
+    private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
         RectF newCrop = null;
-        //Fix opposite corner in place and move sides
+        // Fix opposite corner in place and move sides
         if (moving_corner == BOTTOM_RIGHT) {
             newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
                     + dy);
@@ -434,120 +361,90 @@
     }
 
     private void moveEdges(float dX, float dY) {
-        RectF cropped = getRotatedCropBounds();
-        float minWidthHeight = getScaledMinWidthHeight();
-        float scale = computeScale(getWidth(), getHeight());
-        float deltaX = dX / scale;
-        float deltaY = dY / scale;
-        int select = movingEdges;
-        if (mFixAspectRatio && (select != MOVE_BLOCK)) {
+        RectF crop = mBounded.getInner();
 
-            // TODO: add in orientation change for fixed aspect
-            /*if (select == TOP_LEFT || select == TOP_RIGHT ||
-                    select == BOTTOM_LEFT || select == BOTTOM_RIGHT){
-                RectF blank = new RectF();
-                if(switchCropBounds(select, blank)){
-                    setCropBounds(blank);
-                    return;
-                }
-            }*/
-            if (select == MOVE_LEFT) {
-                select |= MOVE_TOP;
-            }
-            if (select == MOVE_TOP) {
-                select |= MOVE_LEFT;
-            }
-            if (select == MOVE_RIGHT) {
-                select |= MOVE_BOTTOM;
-            }
-            if (select == MOVE_BOTTOM) {
-                select |= MOVE_RIGHT;
-            }
-        }
+        Matrix mc = getCropBoundDisplayedMatrix();
 
-        if (select == MOVE_BLOCK) {
-            RectF straight = getRotatedStraightenBounds();
-            // Move the whole cropped bounds within the photo display bounds.
-            deltaX = (deltaX > 0) ? Math.min(straight.right - cropped.right, deltaX)
-                    : Math.max(straight.left - cropped.left, deltaX);
-            deltaY = (deltaY > 0) ? Math.min(straight.bottom - cropped.bottom, deltaY)
-                    : Math.max(straight.top - cropped.top, deltaY);
-            cropped.offset(deltaX, deltaY);
+        RectF photo = getLocalPhotoBounds();
+        Matrix mp = getPhotoBoundDisplayedMatrix();
+        float[] photoCorners = CropMath.getCornersFromRect(photo);
+        float[] photoCenter = {
+                photo.centerX(), photo.centerY()
+        };
+        mp.mapPoints(photoCorners);
+        mp.mapPoints(photoCenter);
+
+        float minWidthHeight = mMinSideSize;
+
+        if (movingEdges == MOVE_BLOCK) {
+            mBounded.moveInner(-dX, -dY);
+            RectF r = mBounded.getInner();
+            setCropBounds(r);
+            return;
         } else {
             float dx = 0;
             float dy = 0;
 
-            if ((select & MOVE_LEFT) != 0) {
-                dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left;
+            if ((movingEdges & MOVE_LEFT) != 0) {
+                dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left;
             }
-            if ((select & MOVE_TOP) != 0) {
-                dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top;
+            if ((movingEdges & MOVE_TOP) != 0) {
+                dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top;
             }
-            if ((select & MOVE_RIGHT) != 0) {
-                dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight)
-                        - cropped.right;
+            if ((movingEdges & MOVE_RIGHT) != 0) {
+                dx = Math.max(crop.right + dX, crop.left + minWidthHeight)
+                        - crop.right;
             }
-            if ((select & MOVE_BOTTOM) != 0) {
-                dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight)
-                        - cropped.bottom;
+            if ((movingEdges & MOVE_BOTTOM) != 0) {
+                dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight)
+                        - crop.bottom;
             }
 
             if (mFixAspectRatio) {
-                RectF crop = getCropBoundsDisplayed();
-                float [] l1 = {crop.left, crop.bottom};
-                float [] l2 = {crop.right, crop.top};
-                if(movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT){
+                float[] l1 = {
+                        crop.left, crop.bottom
+                };
+                float[] l2 = {
+                        crop.right, crop.top
+                };
+                if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
                     l1[1] = crop.top;
                     l2[1] = crop.bottom;
                 }
-                float[] b = { l1[0] - l2[0], l1[1] - l2[1] };
-                float[] disp = {dx, dy};
+                float[] b = {
+                        l1[0] - l2[0], l1[1] - l2[1]
+                };
+                float[] disp = {
+                        dx, dy
+                };
                 float[] bUnit = GeometryMath.normalize(b);
                 float sp = GeometryMath.scalarProjection(disp, bUnit);
                 dx = sp * bUnit[0];
                 dy = sp * bUnit[1];
-                RectF newCrop = fixedCornerResize(crop, select, dx * scale, dy * scale);
-                Matrix m = getCropBoundDisplayMatrix();
-                Matrix m0 = new Matrix();
-                if (!m.invert(m0)){
-                    if (LOGV)
-                        Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX");
-                    return;
-                }
-                if (!m0.mapRect(newCrop)){
-                    if (LOGV)
-                        Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE");
-                    return;
-                }
+                RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
+
+                mBounded.fixedAspectResizeInner(newCrop);
+                newCrop = mBounded.getInner();
                 setCropBounds(newCrop);
                 return;
             } else {
-                if ((select & MOVE_LEFT) != 0) {
-                    cropped.left += dx;
+                if ((movingEdges & MOVE_LEFT) != 0) {
+                    crop.left += dx;
                 }
-                if ((select & MOVE_TOP) != 0) {
-                    cropped.top += dy;
+                if ((movingEdges & MOVE_TOP) != 0) {
+                    crop.top += dy;
                 }
-                if ((select & MOVE_RIGHT) != 0) {
-                    cropped.right += dx;
+                if ((movingEdges & MOVE_RIGHT) != 0) {
+                    crop.right += dx;
                 }
-                if ((select & MOVE_BOTTOM) != 0) {
-                    cropped.bottom += dy;
+                if ((movingEdges & MOVE_BOTTOM) != 0) {
+                    crop.bottom += dy;
                 }
             }
         }
-        movingEdges = select;
-        Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds());
-        Matrix m0 = new Matrix();
-        if (!m.invert(m0)) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX");
-        }
-        if (!m0.mapRect(cropped)) {
-            if (LOGV)
-                Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS");
-        }
-        setCropBounds(cropped);
+        mBounded.resizeInner(crop);
+        crop = mBounded.getInner();
+        setCropBounds(crop);
     }
 
     private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) {
@@ -560,7 +457,8 @@
     @Override
     protected void setActionDown(float x, float y) {
         super.setActionDown(x, y);
-        detectMovingEdges(x, y);
+        detectMovingEdges(x + mOffset[0], y + mOffset[1]);
+
     }
 
     @Override
@@ -571,20 +469,54 @@
 
     @Override
     protected void setActionMove(float x, float y) {
-        if (movingEdges != 0){
+
+        if (movingEdges != 0) {
             moveEdges(x - mCurrentX, y - mCurrentY);
         }
         super.setActionMove(x, y);
+
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        setActionUp();
+        cropSetup();
+        invalidate();
     }
 
     private void cropSetup() {
+        RectF crop = getLocalCropBounds();
+        Matrix m = getCropBoundDisplayedMatrix();
+        m.mapRect(crop);
         if (mFixAspectRatio) {
-            RectF cb = getRotatedCropBounds();
-            fixAspectRatio(cb, mAspectWidth, mAspectHeight);
-            RectF cb0 = getUnrotatedCropBounds(cb);
-            setCropBounds(cb0);
-        } else {
-            setCropBounds(getLocalCropBounds());
+            CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight);
+        }
+        float dCentX = getWidth() / 2;
+        float dCentY = getHeight() / 2;
+
+        BoundedRect r = getBoundedCrop(crop);
+        crop = r.getInner();
+        if (!setCropBounds(crop)) {
+            float h = mMinSideSize / 2;
+            float wScale = 1;
+            float hScale = mAspectHeight / mAspectWidth;
+            if (hScale < 1) {
+                wScale = mAspectWidth / mAspectHeight;
+                hScale = 1;
+            }
+            crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h
+                    * hScale);
+            if (mFixAspectRatio) {
+                CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight);
+            }
+            r.setInner(crop);
+            crop = r.getInner();
+            if (!setCropBounds(crop)) {
+                crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h);
+                r.setInner(crop);
+                crop = r.getInner();
+                setCropBounds(crop);
+            }
         }
     }
 
@@ -592,7 +524,7 @@
     public void imageLoaded() {
         super.imageLoaded();
         syncLocalToMasterGeometry();
-        applyClear();
+        clear();
         invalidate();
     }
 
@@ -600,7 +532,7 @@
     protected void gainedVisibility() {
         float rot = getLocalRotation();
         // if has changed orientation via rotate
-        if( ((int) ((rot - mLastRot) / 90)) % 2 != 0 ){
+        if (((int) ((rot - mLastRot) / 90)) % 2 != 0) {
             swapAspect();
         }
         cropSetup();
@@ -610,7 +542,6 @@
     @Override
     public void resetParameter() {
         super.resetParameter();
-        cropSetup();
     }
 
     @Override
@@ -635,101 +566,171 @@
 
     @Override
     protected void drawShape(Canvas canvas, Bitmap image) {
-        // TODO: move style to xml
         gPaint.setAntiAlias(true);
-        gPaint.setFilterBitmap(true);
-        gPaint.setDither(true);
         gPaint.setARGB(255, 255, 255, 255);
 
         if (mFirstDraw) {
             cropSetup();
             mFirstDraw = false;
         }
-        float rotation = getLocalRotation();
 
-        RectF crop = drawTransformed(canvas, image, gPaint);
+        RectF crop = drawTransformed(canvas, image, gPaint, mOffset);
         gPaint.setColor(mBorderColor);
         gPaint.setStrokeWidth(3);
         gPaint.setStyle(Paint.Style.STROKE);
-        drawRuleOfThird(canvas, crop, gPaint);
 
-        if (mFixAspectRatio){
-            float w = crop.width();
-            float h = crop.height();
-            float diag = (float) Math.sqrt(w*w + h*h);
+        boolean doThirds = true;
 
-            float dash_len = 20;
-            int num_intervals = (int) (diag / dash_len);
-            float [] tl = { crop.left, crop.top };
-            float centX = tl[0] + w/2;
-            float centY = tl[1] + h/2 + 5;
-            float [] br = { crop.right, crop.bottom };
-            float [] vec = GeometryMath.getUnitVectorFromPoints(tl, br);
-
-            float [] counter = tl;
-            for (int x = 0; x < num_intervals; x++ ){
-                float tempX = counter[0] + vec[0] * dash_len;
-                float tempY = counter[1] + vec[1] * dash_len;
-                if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2){
-                    canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint);
-                }
-                counter[0] = tempX;
-                counter[1] = tempY;
+        if (mFixAspectRatio) {
+            float spotlightX = 0;
+            float spotlightY = 0;
+            if (mCropExtras != null) {
+                spotlightX = mCropExtras.getSpotlightX();
+                spotlightY = mCropExtras.getSpotlightY();
             }
+            if (mDoingCropIntentAction && spotlightX > 0 && spotlightY > 0) {
+                float sx = crop.width() * spotlightX;
+                float sy = crop.height() * spotlightY;
+                float cx = crop.centerX();
+                float cy = crop.centerY();
+                RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+                float temp = sx;
+                sx = sy;
+                sy = temp;
+                RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
+                canvas.drawRect(r1, gPaint);
+                canvas.drawRect(r2, gPaint);
+                doThirds = false;
+            } else {
+                float w = crop.width();
+                float h = crop.height();
+                float diag = (float) Math.sqrt(w * w + h * h);
 
-            gPaint.setTextAlign(Paint.Align.CENTER);
-            gPaint.setTextSize(mAspectTextSize);
-            canvas.drawText(mAspect, centX, centY, gPaint);
+                float dash_len = 20;
+                int num_intervals = (int) (diag / dash_len);
+                float[] tl = {
+                        crop.left, crop.top
+                };
+                float centX = tl[0] + w / 2;
+                float centY = tl[1] + h / 2 + 5;
+                float[] br = {
+                        crop.right, crop.bottom
+                };
+                float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br);
+
+                float[] counter = tl;
+                for (int x = 0; x < num_intervals; x++) {
+                    float tempX = counter[0] + vec[0] * dash_len;
+                    float tempY = counter[1] + vec[1] * dash_len;
+                    if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) {
+                        canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint);
+                    }
+                    counter[0] = tempX;
+                    counter[1] = tempY;
+                }
+
+                gPaint.setTextAlign(Paint.Align.CENTER);
+                gPaint.setTextSize(mAspectTextSize);
+                canvas.drawText(mAspect, centX, centY, gPaint);
+            }
         }
 
-        gPaint.setColor(mBorderColor);
-        gPaint.setStrokeWidth(3);
-        gPaint.setStyle(Paint.Style.STROKE);
-        drawStraighten(canvas, gPaint);
+        if (doThirds) {
+            drawRuleOfThird(canvas, crop, gPaint);
 
-        int decoded_moving = decoder(movingEdges, rotation);
-        canvas.save();
-        canvas.rotate(rotation, mCenterX, mCenterY);
-        RectF scaledCrop = unrotatedCropBounds();
-        boolean notMoving = decoded_moving == 0;
-        if (((decoded_moving & MOVE_TOP) != 0) || notMoving) {
-            drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top);
         }
-        if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) {
-            drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom);
+
+        RectF scaledCrop = crop;
+        boolean notMoving = (movingEdges == 0);
+        if (mFixAspectRatio) {
+            if ((movingEdges == TOP_LEFT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top);
+            }
+            if ((movingEdges == TOP_RIGHT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top);
+            }
+            if ((movingEdges == BOTTOM_LEFT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom);
+            }
+            if ((movingEdges == BOTTOM_RIGHT) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom);
+            }
+        } else {
+            if (((movingEdges & MOVE_TOP) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top);
+            }
+            if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom);
+            }
+            if (((movingEdges & MOVE_LEFT) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY());
+            }
+            if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) {
+                drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY());
+            }
         }
-        if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) {
-            drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY());
-        }
-        if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) {
-            drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY());
-        }
-        canvas.restore();
     }
 
-    private int bitCycleLeft(int x, int times, int d) {
-        int mask = (1 << d) - 1;
-        int mout = x & mask;
-        times %= d;
-        int hi = mout >> (d - times);
-        int low = (mout << times) & mask;
-        int ret = x & ~mask;
-        ret |= low;
-        ret |= hi;
-        return ret;
+    public void setAspectButton(int itemId) {
+        switch (itemId) {
+            case R.id.crop_menu_1to1: {
+                String t = getActivity().getString(R.string.aspect1to1_effect);
+                apply(1, 1);
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_4to3: {
+                String t = getActivity().getString(R.string.aspect4to3_effect);
+                apply(4, 3);
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_3to4: {
+                String t = getActivity().getString(R.string.aspect3to4_effect);
+                apply(3, 4);
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_5to7: {
+                String t = getActivity().getString(R.string.aspect5to7_effect);
+                apply(5, 7);
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_7to5: {
+                String t = getActivity().getString(R.string.aspect7to5_effect);
+                apply(7, 5);
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_none: {
+                String t = getActivity().getString(R.string.aspectNone_effect);
+                applyClear();
+                setAspectString(t);
+                break;
+            }
+            case R.id.crop_menu_original: {
+                String t = getActivity().getString(R.string.aspectOriginal_effect);
+                applyOriginal();
+                setAspectString(t);
+                break;
+            }
+        }
+        invalidate();
     }
 
-    protected int decoder(int movingEdges, float rotation) {
-        int rot = constrainedRotation(rotation);
-        switch (rot) {
-            case 90:
-                return bitCycleLeft(movingEdges, 3, 4);
-            case 180:
-                return bitCycleLeft(movingEdges, 2, 4);
-            case 270:
-                return bitCycleLeft(movingEdges, 1, 4);
-            default:
-                return movingEdges;
-        }
+    public void setFixedAspect(boolean fixedAspect) {
+        mFixedAspect = fixedAspect;
     }
+
+    @Override
+    public boolean useUtilityPanel() {
+        // Only shows the aspect ratio popup if we are not fixed
+        return !mFixedAspect;
+    }
+
+    public void setEditor(EditorCrop editorCrop) {
+        mEditorCrop = editorCrop;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
new file mode 100644
index 0000000..8fcc028
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
@@ -0,0 +1,150 @@
+
+package com.android.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorDraw;
+import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterDraw;
+
+public class ImageDraw extends ImageShow {
+
+    private static final String LOGTAG = "ImageDraw";
+    private int mCurrentColor = Color.RED;
+    final static float INITAL_STROKE_RADIUS = 40;
+    private float mCurrentSize = INITAL_STROKE_RADIUS;
+    private byte mType = 0;
+    private FilterDrawRepresentation mFRep;
+    private EditorDraw mEditorDraw;
+
+    public ImageDraw(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        resetParameter();
+        super.setOriginalDisabled(true);
+    }
+
+    public ImageDraw(Context context) {
+        super(context);
+        resetParameter();
+        super.setOriginalDisabled(true);
+    }
+
+    public void setEditor(EditorDraw editorDraw) {
+        mEditorDraw = editorDraw;
+    }
+    public void setFilterDrawRepresentation(FilterDrawRepresentation fr) {
+        mFRep = fr;
+    }
+
+    public Drawable getIcon(Context context) {
+
+        return null;
+    }
+
+    @Override
+    public void resetParameter() {
+        if (mFRep != null) {
+            mFRep.clear();
+        }
+    }
+
+    public void setColor(int color) {
+        mCurrentColor = color;
+    }
+
+    public void setSize(int size) {
+        mCurrentSize = size;
+    }
+
+    public void setStyle(byte style) {
+        mType = (byte) (style % ImageFilterDraw.NUMBER_OF_STYLES);
+    }
+
+    public int getStyle() {
+        return mType;
+    }
+
+    public int getSize() {
+        return (int) mCurrentSize;
+    }
+
+    @Override
+    public void updateImage() {
+        super.updateImage();
+        invalidate();
+    }
+
+    float[] mTmpPoint = new float[2]; // so we do not malloc
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getPointerCount() > 1) {
+            boolean ret = super.onTouchEvent(event);
+            if (mFRep.getCurrentDrawing() != null) {
+                mFRep.clearCurrentSection();
+                mEditorDraw.commitLocalRepresentation();
+            }
+            return ret;
+        }
+        if (event.getAction() != MotionEvent.ACTION_DOWN) {
+            if (mFRep.getCurrentDrawing() == null) {
+                return super.onTouchEvent(event);
+            }
+        }
+
+        ImageFilterDraw filter = (ImageFilterDraw) getCurrentFilter();
+
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            calcScreenMapping();
+            mTmpPoint[0] = event.getX();
+            mTmpPoint[1] = event.getY();
+            mToOrig.mapPoints(mTmpPoint);
+            mFRep.startNewSection(mType, mCurrentColor, mCurrentSize, mTmpPoint[0], mTmpPoint[1]);
+        }
+
+        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+
+            int historySize = event.getHistorySize();
+            final int pointerCount = event.getPointerCount();
+            for (int h = 0; h < historySize; h++) {
+                int p = 0;
+                {
+                    mTmpPoint[0] = event.getHistoricalX(p, h);
+                    mTmpPoint[1] = event.getHistoricalY(p, h);
+                    mToOrig.mapPoints(mTmpPoint);
+                    mFRep.addPoint(mTmpPoint[0], mTmpPoint[1]);
+                }
+            }
+        }
+
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            mTmpPoint[0] = event.getX();
+            mTmpPoint[1] = event.getY();
+            mToOrig.mapPoints(mTmpPoint);
+            mFRep.endSection(mTmpPoint[0], mTmpPoint[1]);
+        }
+        mEditorDraw.commitLocalRepresentation();
+        invalidate();
+        return true;
+    }
+
+    Matrix mRotateToScreen = new Matrix();
+    Matrix mToOrig;
+    private void calcScreenMapping() {
+        mToOrig = getScreenToImageMatrix(true);
+        mToOrig.invert(mRotateToScreen);
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        calcScreenMapping();
+
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
index 5d6fe50..15197b0 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
@@ -24,6 +24,7 @@
 import android.util.AttributeSet;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
 
 public class ImageFlip extends ImageGeometry {
@@ -32,6 +33,7 @@
     private static final float MIN_FLICK_DIST_FOR_FLIP = 0.1f;
     private static final String LOGTAG = "ImageFlip";
     private FLIP mNextFlip = FLIP.NONE;
+    private EditorFlip mEditorFlip;
 
     public ImageFlip(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -56,6 +58,22 @@
         return (rot / 90) % 2 != 0;
     }
 
+    public void flip() {
+        FLIP flip = getLocalFlip();
+        boolean next = true;
+        // Picks next flip in order from enum FLIP (wrapping)
+        for (FLIP f : FLIP.values()) {
+            if (next) {
+                mNextFlip = f;
+                next = false;
+            }
+            if (f.equals(flip)) {
+                next = true;
+            }
+        }
+        setLocalFlip(mNextFlip);
+    }
+
     @Override
     protected void setActionMove(float x, float y) {
         super.setActionMove(x, y);
@@ -136,10 +154,12 @@
     @Override
     protected void drawShape(Canvas canvas, Bitmap image) {
         gPaint.setAntiAlias(true);
-        gPaint.setFilterBitmap(true);
-        gPaint.setDither(true);
         gPaint.setARGB(255, 255, 255, 255);
         drawTransformedCropped(canvas, image, gPaint);
     }
 
+    public void setEditor(EditorFlip editorFlip) {
+        mEditorFlip = editorFlip;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
index 42dd139..0c51b16 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
@@ -32,8 +32,8 @@
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-public abstract class ImageGeometry extends ImageSlave {
-    private boolean mVisibilityGained = false;
+public abstract class ImageGeometry extends ImageShow {
+    protected boolean mVisibilityGained = false;
     private boolean mHasDrawn = false;
 
     protected static final float MAX_STRAIGHTEN_ANGLE = 45;
@@ -137,8 +137,8 @@
     }
 
     // Overwrites local with master
-    protected void syncLocalToMasterGeometry() {
-        mLocalGeometry = getMaster().getGeometry();
+    public void syncLocalToMasterGeometry() {
+        mLocalGeometry = getGeometry();
         calculateLocalScalingFactorAndOffset();
     }
 
@@ -191,8 +191,8 @@
         return r * 90;
     }
 
-    protected Matrix getLocalGeoFlipMatrix(float width, float height) {
-        return mLocalGeometry.getFlipMatrix(width, height);
+    protected boolean isHeightWidthSwapped() {
+        return ((int) (getLocalRotation() / 90)) % 2 != 0;
     }
 
     protected void setLocalStraighten(float r) {
@@ -217,32 +217,6 @@
         return getLocalRotation() + getLocalStraighten();
     }
 
-    protected static float[] getCornersFromRect(RectF r) {
-        // Order is:
-        // 0------->1
-        // ^        |
-        // |        v
-        // 3<-------2
-        float[] corners = {
-                r.left, r.top, // 0
-                r.right, r.top, // 1
-                r.right, r.bottom,// 2
-                r.left, r.bottom // 3
-        };
-        return corners;
-    }
-
-    // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
-    // image bound rectangle, clamps it to the edge of the rectangle.
-    protected static void getEdgePoints(RectF imageBound, float[] array) {
-        if (array.length < 2)
-            return;
-        for (int x = 0; x < array.length; x += 2) {
-            array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right);
-            array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom);
-        }
-    }
-
     protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) {
         Path crop = new Path();
         crop.moveTo(points[0], points[1]);
@@ -254,16 +228,6 @@
         return crop;
     }
 
-    protected static void fixAspectRatio(RectF r, float w, float h) {
-        float scale = Math.min(r.width() / w, r.height() / h);
-        float centX = r.centerX();
-        float centY = r.centerY();
-        float hw = scale * w / 2;
-        float hh = scale * h / 2;
-        r.set(centX - hw, centY - hh, centX + hw, centY + hh);
-
-    }
-
     protected static float getNewHeightForWidthAspect(float width, float w, float h) {
         return width * h / w;
     }
@@ -277,6 +241,7 @@
         super.onVisibilityChanged(changedView, visibility);
         if (visibility == View.VISIBLE) {
             mVisibilityGained = true;
+            MasterImage.getImage().invalidateFiltersOnly();
             syncLocalToMasterGeometry();
             updateScale();
             gainedVisibility();
@@ -290,11 +255,11 @@
     }
 
     protected void gainedVisibility() {
-        // TODO: Override this stub.
+        // Override this stub.
     }
 
     protected void lostVisibility() {
-        // TODO: Override this stub.
+        // Override this stub.
     }
 
     @Override
@@ -319,15 +284,12 @@
             default:
                 setNoAction();
         }
-        if (getPanelController() != null) {
-            getPanelController().onNewValue(getLocalValue());
-        }
         invalidate();
         return true;
     }
 
     protected int getLocalValue() {
-        return 0; // TODO: Override this
+        return 0; // Override this
     }
 
     protected void setActionDown(float x, float y) {
@@ -362,7 +324,7 @@
     }
 
     public void saveAndSetPreset() {
-        ImagePreset lastHistoryItem = getHistory().getLast();
+        ImagePreset lastHistoryItem = MasterImage.getImage().getHistory().getLast();
         if (lastHistoryItem != null && lastHistoryItem.historyName().equalsIgnoreCase(getName())) {
             getImagePreset().setGeometry(mLocalGeometry);
             resetImageCaches(this);
@@ -372,7 +334,7 @@
                 copy.setGeometry(mLocalGeometry);
                 copy.setHistoryName(getName());
                 copy.setIsFx(false);
-                setImagePreset(copy, true);
+                MasterImage.getImage().setPreset(copy, true);
             }
         }
         invalidate();
@@ -402,110 +364,19 @@
         return new RectF(left, top, right, bottom);
     }
 
-    protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) {
-        RectF pbounds = getLocalPhotoBounds();
-        float scale = GeometryMath
-                .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight());
-        if (((int) (getLocalRotation() / 90)) % 2 != 0) {
-            scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth());
-        }
-        float yoff = getHeight() / 2;
-        float xoff = getWidth() / 2;
-        float w = r.left * 2 + r.width();
-        float h = r.top * 2 + r.height();
-        return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate);
-    }
-
-    protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) {
-        canvas.save();
-        canvas.drawBitmap(bitmap, m, paint);
-        canvas.restore();
-    }
-
-    protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) {
-        float scale = computeScale(getWidth(), getHeight());
-        float yoff = getHeight() / 2;
-        float xoff = getWidth() / 2;
-        Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff);
-        drawImageBitmap(canvas, bitmap, paint, m);
-    }
-
     protected RectF straightenBounds() {
         RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(),
                 getLocalStraighten());
-        Matrix m = getGeoMatrix(bounds, true);
-        m.mapRect(bounds);
-        return bounds;
-    }
-
-    protected void drawStraighten(Canvas canvas, Paint paint) {
-        RectF bounds = straightenBounds();
-        canvas.save();
-        canvas.drawRect(bounds, paint);
-        canvas.restore();
-    }
-
-    protected RectF unrotatedCropBounds() {
-        RectF bounds = getLocalCropBounds();
-        RectF pbounds = getLocalPhotoBounds();
         float scale = computeScale(getWidth(), getHeight());
-        float yoff = getHeight() / 2;
-        float xoff = getWidth() / 2;
-        Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale,
-                xoff, yoff, 0);
-        m.mapRect(bounds);
+        bounds = GeometryMath.scaleRect(bounds, scale);
+        float dx = (getWidth() / 2) - bounds.centerX();
+        float dy = (getHeight() / 2) - bounds.centerY();
+        bounds.offset(dx, dy);
         return bounds;
     }
 
-    protected RectF cropBounds() {
-        RectF bounds = getLocalCropBounds();
-        Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
-        m.mapRect(bounds);
-        return bounds;
-    }
-
-    // Fails for non-90 degree
-    protected void drawCrop(Canvas canvas, Paint paint) {
-        RectF bounds = cropBounds();
-        canvas.save();
-        canvas.drawRect(bounds, paint);
-        canvas.restore();
-    }
-
-    protected void drawCropSafe(Canvas canvas, Paint paint) {
-        Matrix m = getGeoMatrix(getLocalPhotoBounds(), true);
-        RectF crop = getLocalCropBounds();
-        if (!m.rectStaysRect()) {
-            float[] corners = getCornersFromRect(crop);
-            m.mapPoints(corners);
-            drawClosedPath(canvas, paint, corners);
-        } else {
-            m.mapRect(crop);
-            Path path = new Path();
-            path.addRect(crop, Path.Direction.CCW);
-            canvas.drawPath(path, paint);
-        }
-    }
-
-    protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) {
-        paint.setARGB(255, 0, 0, 0);
-        drawImageBitmap(canvas, bitmap, paint);
-        paint.setColor(Color.WHITE);
-        paint.setStyle(Style.STROKE);
-        paint.setStrokeWidth(2);
-        drawCropSafe(canvas, paint);
-        paint.setColor(getDefaultBackgroundColor());
-        paint.setStyle(Paint.Style.FILL);
-        drawShadows(canvas, paint, unrotatedCropBounds());
-    }
-
-    protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
-        RectF display = new RectF(0, 0, getWidth(), getHeight());
-        drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2,
-                getHeight() / 2);
-    }
-
-    protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds,
+    protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds,
+            RectF outerBounds,
             float rotation, float centerX, float centerY) {
         canvas.save();
         canvas.rotate(rotation, centerX, centerY);
@@ -527,19 +398,28 @@
         canvas.restore();
     }
 
+    protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) {
+        float w = getWidth();
+        float h = getHeight();
+        canvas.drawRect(0f, 0f, w, innerBounds.top, p);
+        canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p);
+        canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p);
+        canvas.drawRect(0f, innerBounds.bottom, w, h, p);
+    }
+
     @Override
     public void onDraw(Canvas canvas) {
         if (getDirtyGeometryFlag()) {
             syncLocalToMasterGeometry();
             clearDirtyGeometryFlag();
         }
-        requestFilteredImages();
-        Bitmap image = getMaster().getFiltersOnlyImage();
+        Bitmap image = getFiltersOnlyImage();
         if (image == null) {
             invalidate();
             return;
         }
         mHasDrawn = true;
+
         drawShape(canvas, image);
     }
 
@@ -547,21 +427,38 @@
         // TODO: Override this stub.
     }
 
-    protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) {
-        p.setARGB(255, 0, 0, 0);
+    /**
+     * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix
+     * and returns the scale factor.
+     */
+    protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) {
         RectF photoBounds = getLocalPhotoBounds();
         RectF cropBounds = getLocalCropBounds();
         float scale = computeScale(getWidth(), getHeight());
         // checks if local rotation is an odd multiple of 90.
-        if (((int) (getLocalRotation() / 90)) % 2 != 0) {
+        if (isHeightWidthSwapped()) {
             scale = computeScale(getHeight(), getWidth());
         }
         // put in screen coordinates
-        RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale);
-        RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale);
-        float[] displayCenter = {
-                getWidth() / 2f, getHeight() / 2f
-        };
+        if (crop != null) {
+            crop.set(GeometryMath.scaleRect(cropBounds, scale));
+        }
+        if (photo != null) {
+            photo.set(GeometryMath.scaleRect(photoBounds, scale));
+        }
+        if (displayCenter != null && displayCenter.length >= 2) {
+            displayCenter[0] = getWidth() / 2f;
+            displayCenter[1] = getHeight() / 2f;
+        }
+        return scale;
+    }
+
+    protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) {
+        p.setARGB(255, 0, 0, 0);
+        float[] displayCenter = new float[2];
+        RectF scaledCrop = new RectF();
+        RectF scaledPhoto = new RectF();
+        float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
         Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
 
@@ -569,9 +466,11 @@
                 getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
         m1.mapRect(scaledCrop);
         Path path = new Path();
+        scaledCrop.offset(-offset[0], -offset[1]);
         path.addRect(scaledCrop, Path.Direction.CCW);
 
         m.preScale(scale, scale);
+        m.postTranslate(-offset[0], -offset[1]);
         canvas.save();
         canvas.drawBitmap(photo, m, p);
         canvas.restore();
@@ -580,6 +479,11 @@
         p.setStyle(Style.STROKE);
         p.setStrokeWidth(2);
         canvas.drawPath(path, p);
+
+        p.setColor(getDefaultBackgroundColor());
+        p.setAlpha(128);
+        p.setStyle(Paint.Style.FILL);
+        drawShadows(canvas, p, scaledCrop);
         return scaledCrop;
     }
 
@@ -590,7 +494,7 @@
         float imageHeight = cropBounds.height();
         float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight());
         // checks if local rotation is an odd multiple of 90.
-        if (((int) (getLocalRotation() / 90)) % 2 != 0) {
+        if (isHeightWidthSwapped()) {
             scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth());
         }
         // put in screen coordinates
@@ -618,6 +522,8 @@
         p.setStyle(Paint.Style.FILL);
         scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1]
                 - scaledCrop.centerY());
-        drawShadows(canvas, p, scaledCrop);
+        RectF display = new RectF(0, 0, getWidth(), getHeight());
+        drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2,
+                getHeight() / 2);
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
new file mode 100644
index 0000000..c58dd5f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImagePoint.java
@@ -0,0 +1,95 @@
+/*
+ * 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.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.util.AttributeSet;
+
+import com.android.gallery3d.filtershow.editors.EditorRedEye;
+import com.android.gallery3d.filtershow.filters.FilterPoint;
+import com.android.gallery3d.filtershow.filters.FilterRedEyeRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilterRedEye;
+
+public abstract class ImagePoint extends ImageShow {
+
+    private static final String LOGTAG = "ImageRedEyes";
+    protected EditorRedEye mEditorRedEye;
+    protected FilterRedEyeRepresentation mRedEyeRep;
+    protected static float mTouchPadding = 80;
+
+    public static void setTouchPadding(float padding) {
+        mTouchPadding = padding;
+    }
+
+    public ImagePoint(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public ImagePoint(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void resetParameter() {
+        ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter();
+        if (filter != null) {
+            filter.clear();
+        }
+        invalidate();
+    }
+
+    @Override
+    public void updateImage() {
+        super.updateImage();
+        invalidate();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        Paint paint = new Paint();
+        paint.setStyle(Style.STROKE);
+        paint.setColor(Color.RED);
+        paint.setStrokeWidth(2);
+
+        Matrix originalToScreen = getImageToScreenMatrix(false);
+        Matrix originalRotateToScreen = getImageToScreenMatrix(true);
+
+        if (mRedEyeRep != null) {
+            for (FilterPoint candidate : mRedEyeRep.getCandidates()) {
+                drawPoint(candidate, canvas, originalToScreen, originalRotateToScreen, paint);
+            }
+        }
+    }
+
+    protected abstract void drawPoint(
+            FilterPoint candidate, Canvas canvas, Matrix originalToScreen,
+            Matrix originalRotateToScreen, Paint paint);
+
+    public void setEditor(EditorRedEye editorRedEye) {
+        mEditorRedEye = editorRedEye;
+    }
+
+    public void setRepresentation(FilterRedEyeRepresentation redEyeRep) {
+        mRedEyeRep = redEyeRep;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java
new file mode 100644
index 0000000..40433a0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEye.java
@@ -0,0 +1,137 @@
+/*
+ * 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.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.RectF;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.filters.FilterPoint;
+import com.android.gallery3d.filtershow.filters.RedEyeCandidate;
+
+public class ImageRedEye extends ImagePoint {
+    private static final String LOGTAG = "ImageRedEyes";
+    private RectF mCurrentRect = null;
+
+    public ImageRedEye(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void resetParameter() {
+        super.resetParameter();
+        invalidate();
+    }
+
+    @Override
+
+    public boolean onTouchEvent(MotionEvent event) {
+        super.onTouchEvent(event);
+
+        if (event.getPointerCount() > 1) {
+            return true;
+        }
+
+        if (didFinishScalingOperation()) {
+            return true;
+        }
+
+        float ex = event.getX();
+        float ey = event.getY();
+
+        // let's transform (ex, ey) to displayed image coordinates
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            mCurrentRect = new RectF();
+            mCurrentRect.left = ex - mTouchPadding;
+            mCurrentRect.top = ey - mTouchPadding;
+        }
+        if (event.getAction() == MotionEvent.ACTION_MOVE) {
+            mCurrentRect.right = ex + mTouchPadding;
+            mCurrentRect.bottom = ey + mTouchPadding;
+        }
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            if (mCurrentRect != null) {
+                // transform to original coordinates
+                Matrix originalNoRotateToScreen = getImageToScreenMatrix(false);
+                Matrix originalToScreen = getImageToScreenMatrix(true);
+                Matrix invert = new Matrix();
+                originalToScreen.invert(invert);
+                RectF r = new RectF(mCurrentRect);
+                invert.mapRect(r);
+                RectF r2 = new RectF(mCurrentRect);
+                invert.reset();
+                originalNoRotateToScreen.invert(invert);
+                invert.mapRect(r2);
+                mRedEyeRep.addRect(r, r2);
+                this.resetImageCaches(this);
+            }
+            mCurrentRect = null;
+        }
+        mEditorRedEye.commitLocalRepresentation();
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        Paint paint = new Paint();
+        paint.setStyle(Style.STROKE);
+        paint.setColor(Color.RED);
+        paint.setStrokeWidth(2);
+        if (mCurrentRect != null) {
+            paint.setColor(Color.RED);
+            RectF drawRect = new RectF(mCurrentRect);
+            canvas.drawRect(drawRect, paint);
+        }
+    }
+
+    @Override
+    protected void drawPoint(FilterPoint point, Canvas canvas, Matrix originalToScreen,
+            Matrix originalRotateToScreen, Paint paint) {
+        RedEyeCandidate candidate = (RedEyeCandidate) point;
+        RectF rect = candidate.getRect();
+        RectF drawRect = new RectF();
+        originalToScreen.mapRect(drawRect, rect);
+        RectF fullRect = new RectF();
+        originalRotateToScreen.mapRect(fullRect, rect);
+        paint.setColor(Color.BLUE);
+        canvas.drawRect(fullRect, paint);
+        canvas.drawLine(fullRect.centerX(), fullRect.top,
+                fullRect.centerX(), fullRect.bottom, paint);
+        canvas.drawLine(fullRect.left, fullRect.centerY(),
+                fullRect.right, fullRect.centerY(), paint);
+        paint.setColor(Color.GREEN);
+        float dw = drawRect.width();
+        float dh = drawRect.height();
+        float dx = fullRect.centerX() - dw / 2;
+        float dy = fullRect.centerY() - dh / 2;
+        drawRect.set(dx, dy, dx + dw, dy + dh);
+        canvas.drawRect(drawRect, paint);
+        canvas.drawLine(drawRect.centerX(), drawRect.top,
+                drawRect.centerX(), drawRect.bottom, paint);
+        canvas.drawLine(drawRect.left, drawRect.centerY(),
+                drawRect.right, drawRect.centerY(), paint);
+        canvas.drawCircle(drawRect.centerX(), drawRect.centerY(),
+                mTouchPadding, paint);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
index a4131ff..ab8023e 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
@@ -23,6 +23,7 @@
 import android.util.AttributeSet;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
 
 public class ImageRotate extends ImageGeometry {
 
@@ -30,6 +31,7 @@
     private float mAngle = 0;
 
     private final boolean mSnapToNinety = true;
+    private EditorRotate mEditorRotate;
     private static final String LOGTAG = "ImageRotate";
 
     public ImageRotate(Context context, AttributeSet attrs) {
@@ -52,6 +54,13 @@
         mAngle = (mBaseAngle - angle) % 360;
     }
 
+    public void rotate() {
+        mAngle += 90;
+        mAngle = snappedAngle(mAngle);
+        mAngle %= 360;
+        setLocalRotation(mAngle);
+    }
+
     @Override
     protected void setActionDown(float x, float y) {
         super.setActionDown(x, y);
@@ -74,16 +83,18 @@
     }
 
     @Override
-    protected int getLocalValue() {
+    public int getLocalValue() {
         return constrainedRotation(snappedAngle(getLocalRotation()));
     }
 
     @Override
     protected void drawShape(Canvas canvas, Bitmap image) {
         gPaint.setAntiAlias(true);
-        gPaint.setFilterBitmap(true);
-        gPaint.setDither(true);
         gPaint.setARGB(255, 255, 255, 255);
         drawTransformedCropped(canvas, image, gPaint);
     }
+
+    public void setEditor(EditorRotate editorRotate) {
+        mEditorRotate = editorRotate;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
index 0145c24..b833cf8 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java
@@ -20,71 +20,52 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.net.Uri;
 import android.os.Handler;
 import android.util.AttributeSet;
 import android.view.GestureDetector;
 import android.view.GestureDetector.OnDoubleTapListener;
 import android.view.GestureDetector.OnGestureListener;
 import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
 import android.view.View;
-import android.widget.ArrayAdapter;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.LinearLayout;
 
-import com.android.gallery3d.R;
 import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.ImageStateAdapter;
-import com.android.gallery3d.filtershow.PanelController;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
-import com.android.gallery3d.filtershow.ui.SliderController;
-import com.android.gallery3d.filtershow.ui.SliderListener;
 
 import java.io.File;
 
 public class ImageShow extends View implements OnGestureListener,
-        OnDoubleTapListener,
-        SliderListener,
-        OnSeekBarChangeListener {
+        ScaleGestureDetector.OnScaleGestureListener,
+        OnDoubleTapListener {
 
     private static final String LOGTAG = "ImageShow";
+    private static final boolean ENABLE_ZOOMED_COMPARISON = false;
 
     protected Paint mPaint = new Paint();
     protected static int mTextSize = 24;
     protected static int mTextPadding = 20;
 
-    protected ImagePreset mImagePreset = null;
-    protected ImagePreset mImageGeometryOnlyPreset = null;
-    protected ImagePreset mImageFiltersOnlyPreset = null;
-
     protected ImageLoader mImageLoader = null;
-    private ImageFilter mCurrentFilter = null;
     private boolean mDirtyGeometry = false;
 
     private Bitmap mBackgroundImage = null;
     private final boolean USE_BACKGROUND_IMAGE = false;
     private static int mBackgroundColor = Color.RED;
 
-    private Bitmap mGeometryOnlyImage = null;
-    private Bitmap mFiltersOnlyImage = null;
-    private Bitmap mFilteredImage = null;
-
-    private final boolean USE_SLIDER_GESTURE = false; // set to true to have
-                                                      // slider gesture
-    protected SliderController mSliderController = new SliderController();
-
     private GestureDetector mGestureDetector = null;
+    private ScaleGestureDetector mScaleGestureDetector = null;
 
-    private HistoryAdapter mHistoryAdapter = null;
-    private ImageStateAdapter mImageStateAdapter = null;
-
-    private Rect mImageBounds = new Rect();
-
+    protected Rect mImageBounds = new Rect();
+    private boolean mOriginalDisabled = false;
     private boolean mTouchShowOriginal = false;
     private long mTouchShowOriginalDate = 0;
     private final long mTouchShowOriginalDelayMin = 200; // 200ms
@@ -93,38 +74,41 @@
     private static int UNVEIL_HORIZONTAL = 1;
     private static int UNVEIL_VERTICAL = 2;
 
-    private int mTouchDownX = 0;
-    private int mTouchDownY = 0;
-    protected float mTouchX = 0;
-    protected float mTouchY = 0;
+    private Point mTouchDown = new Point();
+    private Point mTouch = new Point();
+    private boolean mFinishedScalingOperation = false;
 
     private static int mOriginalTextMargin = 8;
     private static int mOriginalTextSize = 26;
     private static String mOriginalText = "Original";
+    private boolean mZoomIn = false;
+    Point mOriginalTranslation = new Point();
+    float mOriginalScale;
+    float mStartFocusX, mStartFocusY;
+    private enum InteractionMode {
+        NONE,
+        SCALE,
+        MOVE
+    }
+    private String mToast = null;
+    private boolean mShowToast = false;
+    private boolean mImportantToast = false;
+    InteractionMode mInteractionMode = InteractionMode.NONE;
 
     protected GeometryMetadata getGeometry() {
         return new GeometryMetadata(getImagePreset().mGeoData);
     }
 
-    public void setGeometry(GeometryMetadata d) {
-        getImagePreset().mGeoData.set(d);
-    }
-
-    private boolean mShowControls = false;
-    private boolean mShowOriginal = false;
-    private String mToast = null;
-    private boolean mShowToast = false;
-    private boolean mImportantToast = false;
-
-    private SeekBar mSeekBar = null;
-    private PanelController mController = null;
-
     private FilterShowActivity mActivity = null;
 
     public static void setDefaultBackgroundColor(int value) {
         mBackgroundColor = value;
     }
 
+    public FilterShowActivity getActivity() {
+        return mActivity;
+    }
+
     public int getDefaultBackgroundColor() {
         return mBackgroundColor;
     }
@@ -152,39 +136,9 @@
     private final Handler mHandler = new Handler();
 
     public void select() {
-        if (getCurrentFilter() != null) {
-            int parameter = getCurrentFilter().getParameter();
-            int maxp = getCurrentFilter().getMaxParameter();
-            int minp = getCurrentFilter().getMinParameter();
-            updateSeekBar(parameter, minp, maxp);
-        }
-        if (mSeekBar != null) {
-            mSeekBar.setOnSeekBarChangeListener(this);
-        }
-    }
-
-    private int parameterToUI(int parameter, int minp, int maxp, int uimax) {
-        return (uimax * (parameter - minp)) / (maxp - minp);
-    }
-
-    private int uiToParameter(int ui, int minp, int maxp, int uimax) {
-        return ((maxp - minp) * ui) / uimax + minp;
-    }
-
-    public void updateSeekBar(int parameter, int minp, int maxp) {
-        if (mSeekBar == null) {
-            return;
-        }
-        int seekMax = mSeekBar.getMax();
-        int progress = parameterToUI(parameter, minp, maxp, seekMax);
-        mSeekBar.setProgress(progress);
-        if (getPanelController() != null) {
-            getPanelController().onNewValue(parameter);
-        }
     }
 
     public void unselect() {
-
     }
 
     public boolean hasModifications() {
@@ -195,80 +149,37 @@
     }
 
     public void resetParameter() {
-        ImageFilter currentFilter = getCurrentFilter();
-        if (currentFilter != null) {
-            onNewValue(currentFilter.getDefaultParameter());
-        }
-        if (USE_SLIDER_GESTURE) {
-            mSliderController.reset();
-        }
+        // TODO: implement reset
     }
 
-    public void setPanelController(PanelController controller) {
-        mController = controller;
-    }
-
-    public PanelController getPanelController() {
-        return mController;
-    }
-
-    @Override
     public void onNewValue(int parameter) {
-        int maxp = 100;
-        int minp = -100;
-        if (getCurrentFilter() != null) {
-            getCurrentFilter().setParameter(parameter);
-            maxp = getCurrentFilter().getMaxParameter();
-            minp = getCurrentFilter().getMinParameter();
-        }
-        if (getImagePreset() != null) {
-            mImageLoader.resetImageForPreset(getImagePreset(), this);
-            getImagePreset().fillImageStateAdapter(mImageStateAdapter);
-        }
-        if (getPanelController() != null) {
-            getPanelController().onNewValue(parameter);
-        }
-        updateSeekBar(parameter, minp, maxp);
         invalidate();
+        mActivity.enableSave(hasModifications());
     }
 
-    @Override
-    public void onTouchDown(float x, float y) {
-        mTouchX = x;
-        mTouchY = y;
-        invalidate();
-    }
-
-    @Override
-    public void onTouchUp() {
+    public Point getTouchPoint() {
+        return mTouch;
     }
 
     public ImageShow(Context context, AttributeSet attrs) {
         super(context, attrs);
-        if (USE_SLIDER_GESTURE) {
-            mSliderController.setListener(this);
-        }
-        mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
-                R.id.rowTextView);
-        mImageStateAdapter = new ImageStateAdapter(context,
-                R.layout.filtershow_imagestate_row);
+
         setupGestureDetector(context);
         mActivity = (FilterShowActivity) context;
+        MasterImage.getImage().addObserver(this);
     }
 
     public ImageShow(Context context) {
         super(context);
-        if (USE_SLIDER_GESTURE) {
-            mSliderController.setListener(this);
-        }
-        mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
-                R.id.rowTextView);
+
         setupGestureDetector(context);
         mActivity = (FilterShowActivity) context;
+        MasterImage.getImage().addObserver(this);
     }
 
     public void setupGestureDetector(Context context) {
         mGestureDetector = new GestureDetector(context, this);
+        mScaleGestureDetector = new ScaleGestureDetector(context, this);
     }
 
     @Override
@@ -276,26 +187,10 @@
         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
         setMeasuredDimension(parentWidth, parentHeight);
-        if (USE_SLIDER_GESTURE) {
-            mSliderController.setWidth(parentWidth);
-            mSliderController.setHeight(parentHeight);
-        }
-    }
-
-    public void setSeekBar(SeekBar seekBar) {
-        mSeekBar = seekBar;
-    }
-
-    public void setCurrentFilter(ImageFilter filter) {
-        mCurrentFilter = filter;
     }
 
     public ImageFilter getCurrentFilter() {
-        return mCurrentFilter;
-    }
-
-    public void setAdapter(HistoryAdapter adapter) {
-        mHistoryAdapter = adapter;
+        return MasterImage.getImage().getCurrentFilter();
     }
 
     public void showToast(String text) {
@@ -323,12 +218,52 @@
         return dst;
     }
 
+    public Rect getImageCropBounds() {
+        return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds());
+    }
+
+    /* consider moving the following 2 methods into a subclass */
+    /**
+     * This function calculates a Image to Screen Transformation matrix
+     *
+     * @param reflectRotation set true if you want the rotation encoded
+     * @return Image to Screen transformation matrix
+     */
+    protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
+        GeometryMetadata geo = getImagePreset().mGeoData;
+        if (geo == null || mImageLoader == null
+                || mImageLoader.getOriginalBounds() == null) {
+            return new Matrix();
+        }
+        Matrix m = geo.getOriginalToScreen(reflectRotation,
+                mImageLoader.getOriginalBounds().width(),
+                mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
+        Point translate = MasterImage.getImage().getTranslation();
+        float scaleFactor = MasterImage.getImage().getScaleFactor();
+        m.postTranslate(translate.x, translate.y);
+        m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
+        return m;
+    }
+
+    /**
+     * This function calculates a to Screen Image Transformation matrix
+     *
+     * @param reflectRotation set true if you want the rotation encoded
+     * @return Screen to Image transformation matrix
+     */
+    protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
+        Matrix m = getImageToScreenMatrix(reflectRotation);
+        Matrix invert = new Matrix();
+        m.invert(invert);
+        return invert;
+    }
+
     public Rect getDisplayedImageBounds() {
         return mImageBounds;
     }
 
     public ImagePreset getImagePreset() {
-        return mImagePreset;
+        return MasterImage.getImage().getPreset();
     }
 
     public void drawToast(Canvas canvas) {
@@ -355,16 +290,43 @@
         }
     }
 
-    public void defaultDrawImage(Canvas canvas) {
-        drawImage(canvas, getFilteredImage());
-        drawPartialImage(canvas, getGeometryOnlyImage());
-    }
-
     @Override
     public void onDraw(Canvas canvas) {
+        MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
+
+        float cx = canvas.getWidth()/2.0f;
+        float cy = canvas.getHeight()/2.0f;
+        float scaleFactor = MasterImage.getImage().getScaleFactor();
+        Point translation = MasterImage.getImage().getTranslation();
+
+        Matrix scalingMatrix = new Matrix();
+        scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
+        scalingMatrix.preTranslate(translation.x, translation.y);
+
+        RectF unscaledClipRect = new RectF(mImageBounds);
+        scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
+
+        canvas.save();
+
+        boolean enablePartialRendering = false;
+
+        // For now, partial rendering is disabled for all filters,
+        // so no need to clip.
+        if (enablePartialRendering && !unscaledClipRect.isEmpty()) {
+            canvas.clipRect(unscaledClipRect);
+        }
+
+        canvas.save();
+        // TODO: center scale on gesture
+        canvas.scale(scaleFactor, scaleFactor, cx, cy);
+        canvas.translate(translation.x, translation.y);
         drawBackground(canvas);
-        requestFilteredImages();
-        defaultDrawImage(canvas);
+        drawImage(canvas, getFilteredImage(), true);
+        Bitmap highresPreview = MasterImage.getImage().getHighresImage();
+        if (highresPreview != null) {
+            drawImage(canvas, highresPreview, false);
+        }
+        canvas.restore();
 
         if (showTitle() && getImagePreset() != null) {
             mPaint.setARGB(200, 0, 0, 0);
@@ -377,12 +339,21 @@
                     1.5f * mTextPadding, mPaint);
         }
 
-        if (showControls()) {
-            if (USE_SLIDER_GESTURE) {
-                mSliderController.onDraw(canvas);
-            }
+        Bitmap partialPreview = MasterImage.getImage().getPartialImage();
+        if (partialPreview != null) {
+            Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
+            Rect dest = new Rect(0, 0, getWidth(), getHeight());
+            canvas.drawBitmap(partialPreview, src, dest, mPaint);
         }
 
+        canvas.save();
+        canvas.scale(scaleFactor, scaleFactor, cx, cy);
+        canvas.translate(translation.x, translation.y);
+        drawPartialImage(canvas, getGeometryOnlyImage());
+        canvas.restore();
+
+        canvas.restore();
+
         drawToast(canvas);
     }
 
@@ -390,84 +361,22 @@
         if (mImageLoader == null) {
             return;
         }
-        updateImagePresets(true);
-    }
-
-    public void updateImagePresets(boolean force) {
-        ImagePreset preset = getImagePreset();
-        if (preset == null) {
-            return;
-        }
-        if (force) {
-            mImageLoader.resetImageForPreset(getImagePreset(), this);
-        }
-        if (force || mImageGeometryOnlyPreset == null) {
-            ImagePreset newPreset = new ImagePreset(preset);
-            newPreset.setDoApplyFilters(false);
-            if (mImageGeometryOnlyPreset == null
-                    || !newPreset.same(mImageGeometryOnlyPreset)) {
-                mImageGeometryOnlyPreset = newPreset;
-                mGeometryOnlyImage = null;
-            }
-        }
-        if (force || mImageFiltersOnlyPreset == null) {
-            ImagePreset newPreset = new ImagePreset(preset);
-            newPreset.setDoApplyGeometry(false);
-            if (mImageFiltersOnlyPreset == null
-                    || !newPreset.same(mImageFiltersOnlyPreset)) {
-                mImageFiltersOnlyPreset = newPreset;
-                mFiltersOnlyImage = null;
-            }
-        }
-    }
-
-    public void requestFilteredImages() {
-        if (mImageLoader != null) {
-            Bitmap bitmap = mImageLoader.getImageForPreset(this,
-                    getImagePreset(), showHires());
-
-            if (bitmap != null) {
-                if (mFilteredImage == null) {
-                    invalidate();
-                }
-                mFilteredImage = bitmap;
-            }
-
-            updateImagePresets(false);
-            if (mImageGeometryOnlyPreset != null) {
-                bitmap = mImageLoader.getImageForPreset(this, mImageGeometryOnlyPreset,
-                        showHires());
-                if (bitmap != null) {
-                    mGeometryOnlyImage = bitmap;
-                }
-            }
-            if (mImageFiltersOnlyPreset != null) {
-                bitmap = mImageLoader.getImageForPreset(this, mImageFiltersOnlyPreset,
-                        showHires());
-                if (bitmap != null) {
-                    mFiltersOnlyImage = bitmap;
-                }
-            }
-        }
-
-        if (mShowOriginal) {
-            mFilteredImage = mGeometryOnlyImage;
-        }
+        MasterImage.getImage().updatePresets(true);
     }
 
     public Bitmap getFiltersOnlyImage() {
-        return mFiltersOnlyImage;
+        return MasterImage.getImage().getFiltersOnlyImage();
     }
 
     public Bitmap getGeometryOnlyImage() {
-        return mGeometryOnlyImage;
+        return MasterImage.getImage().getGeometryOnlyImage();
     }
 
     public Bitmap getFilteredImage() {
-        return mFilteredImage;
+        return MasterImage.getImage().getFilteredImage();
     }
 
-    public void drawImage(Canvas canvas, Bitmap image) {
+    public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
         if (image != null) {
             Rect s = new Rect(0, 0, image.getWidth(),
                     image.getHeight());
@@ -482,18 +391,21 @@
 
             Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
                     (int) (h + ty));
-            mImageBounds = d;
+            if (updateBounds) {
+                mImageBounds = d;
+            }
             canvas.drawBitmap(image, s, d, mPaint);
         }
     }
 
     public void drawPartialImage(Canvas canvas, Bitmap image) {
-        if (!mTouchShowOriginal)
+        boolean showsOriginal = MasterImage.getImage().showsOriginal();
+        if (!showsOriginal && !mTouchShowOriginal)
             return;
         canvas.save();
         if (image != null) {
             if (mShowOriginalDirection == 0) {
-                if ((mTouchY - mTouchDownY) > (mTouchX - mTouchDownX)) {
+                if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
                     mShowOriginalDirection = UNVEIL_VERTICAL;
                 } else {
                     mShowOriginalDirection = UNVEIL_HORIZONTAL;
@@ -504,33 +416,42 @@
             int py = 0;
             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
                 px = mImageBounds.width();
-                py = (int) (mTouchY - mImageBounds.top);
+                py = (int) (mTouch.y - mImageBounds.top);
             } else {
-                px = (int) (mTouchX - mImageBounds.left);
+                px = (int) (mTouch.x - mImageBounds.left);
                 py = mImageBounds.height();
+                if (showsOriginal) {
+                    px = mImageBounds.width();
+                }
             }
 
             Rect d = new Rect(mImageBounds.left, mImageBounds.top,
                     mImageBounds.left + px, mImageBounds.top + py);
             canvas.clipRect(d);
-            drawImage(canvas, image);
+            drawImage(canvas, image, false);
             Paint paint = new Paint();
             paint.setColor(Color.BLACK);
+            paint.setStrokeWidth(3);
 
             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
-                canvas.drawLine(mImageBounds.left, mTouchY - 1,
-                        mImageBounds.right, mTouchY - 1, paint);
+                canvas.drawLine(mImageBounds.left, mTouch.y,
+                        mImageBounds.right, mTouch.y, paint);
             } else {
-                canvas.drawLine(mTouchX - 1, mImageBounds.top,
-                        mTouchX - 1, mImageBounds.bottom, paint);
+                canvas.drawLine(mTouch.x, mImageBounds.top,
+                        mTouch.x, mImageBounds.bottom, paint);
             }
 
             Rect bounds = new Rect();
+            paint.setAntiAlias(true);
             paint.setTextSize(mOriginalTextSize);
             paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
             paint.setColor(Color.BLACK);
-            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin + 1,
-                    mImageBounds.top + bounds.height() + mOriginalTextMargin + 1, paint);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeWidth(3);
+            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
+                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
+            paint.setStyle(Paint.Style.FILL);
+            paint.setStrokeWidth(1);
             paint.setColor(Color.WHITE);
             canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
                     mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
@@ -550,63 +471,19 @@
                 canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
             }
         } else {
-            canvas.drawColor(mBackgroundColor);
+            canvas.drawARGB(0, 0, 0, 0);
         }
     }
 
-    public ImageShow setShowControls(boolean value) {
-        mShowControls = value;
-        if (mShowControls) {
-            if (mSeekBar != null) {
-                mSeekBar.setVisibility(View.VISIBLE);
-            }
-        } else {
-            if (mSeekBar != null) {
-                mSeekBar.setVisibility(View.INVISIBLE);
-            }
-        }
-        return this;
-    }
-
-    public boolean showControls() {
-        return mShowControls;
-    }
-
-    public boolean showHires() {
-        return true;
-    }
-
     public boolean showTitle() {
         return false;
     }
 
-    public void setImagePreset(ImagePreset preset) {
-        setImagePreset(preset, true);
-    }
-
-    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
-        if (preset == null) {
-            return;
-        }
-        mImagePreset = preset;
-        getImagePreset().setImageLoader(mImageLoader);
-        updateImagePresets(true);
-        if (addToHistory) {
-            mHistoryAdapter.addHistoryItem(getImagePreset());
-        }
-        getImagePreset().setEndpoint(this);
-        updateImage();
-        mImagePreset.fillImageStateAdapter(mImageStateAdapter);
-        invalidate();
-    }
-
     public void setImageLoader(ImageLoader loader) {
         mImageLoader = loader;
         if (mImageLoader != null) {
             mImageLoader.addListener(this);
-            if (mImagePreset != null) {
-                mImagePreset.setImageLoader(mImageLoader);
-            }
+            MasterImage.getImage().setImageLoader(mImageLoader);
         }
     }
 
@@ -635,7 +512,7 @@
         RectF r = new RectF(0, 0, w, h);
         getImagePreset().mGeoData.setPhotoBounds(r);
         getImagePreset().mGeoData.setCropBounds(r);
-        setDirtyGeometryFlag();
+
     }
 
     public boolean updateGeometryFlags() {
@@ -643,13 +520,13 @@
     }
 
     public void updateImage() {
+        invalidate();
         if (!updateGeometryFlags()) {
             return;
         }
         Bitmap bitmap = mImageLoader.getOriginalBitmapLarge();
         if (bitmap != null) {
             imageSizeChanged(bitmap);
-            invalidate();
         }
     }
 
@@ -658,126 +535,118 @@
         invalidate();
     }
 
-    public void updateFilteredImage(Bitmap bitmap) {
-        mFilteredImage = bitmap;
-    }
-
     public void saveImage(FilterShowActivity filterShowActivity, File file) {
         mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
     }
 
+    public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) {
+        mImageLoader.saveToUri(f, u, m, filterShowActivity);
+    }
+
+    public void returnFilteredResult(FilterShowActivity filterShowActivity) {
+        mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity);
+    }
+
+    public boolean scaleInProgress() {
+        return mScaleGestureDetector.isInProgress();
+    }
+
+    protected boolean isOriginalDisabled() {
+        return mOriginalDisabled;
+    }
+
+    protected void setOriginalDisabled(boolean originalDisabled) {
+        mOriginalDisabled = originalDisabled;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         super.onTouchEvent(event);
-        if (USE_SLIDER_GESTURE) {
-            mSliderController.onTouchEvent(event);
+        int action = event.getAction();
+        action = action & MotionEvent.ACTION_MASK;
+
+        mGestureDetector.onTouchEvent(event);
+        boolean scaleInProgress = scaleInProgress();
+        mScaleGestureDetector.onTouchEvent(event);
+        if (mInteractionMode == InteractionMode.SCALE) {
+            return true;
         }
-        if (mGestureDetector != null) {
-            mGestureDetector.onTouchEvent(event);
+        if (!scaleInProgress() && scaleInProgress) {
+            // If we were scaling, the scale will stop but we will
+            // still issue an ACTION_UP. Let the subclasses know.
+            mFinishedScalingOperation = true;
         }
+
         int ex = (int) event.getX();
         int ey = (int) event.getY();
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mTouchDownX = ex;
-            mTouchDownY = ey;
+        if (action == MotionEvent.ACTION_DOWN) {
+            mInteractionMode = InteractionMode.MOVE;
+            mTouchDown.x = ex;
+            mTouchDown.y = ey;
             mTouchShowOriginalDate = System.currentTimeMillis();
             mShowOriginalDirection = 0;
+            MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
         }
-        if (event.getAction() == MotionEvent.ACTION_MOVE) {
-            mTouchX = ex;
-            mTouchY = ey;
-            if (!mActivity.isShowingHistoryPanel()
+
+        if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
+            mTouch.x = ex;
+            mTouch.y = ey;
+
+            float scaleFactor = MasterImage.getImage().getScaleFactor();
+            if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
+                float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
+                float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
+                Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
+                Point translation = MasterImage.getImage().getTranslation();
+                translation.x = (int) (originalTranslation.x + translateX);
+                translation.y = (int) (originalTranslation.y + translateY);
+                MasterImage.getImage().setTranslation(translation);
+                mTouchShowOriginal = false;
+            } else if (enableComparison() && !mOriginalDisabled
                     && (System.currentTimeMillis() - mTouchShowOriginalDate
-                    > mTouchShowOriginalDelayMin)) {
+                            > mTouchShowOriginalDelayMin)
+                    && event.getPointerCount() == 1) {
                 mTouchShowOriginal = true;
             }
         }
-        if (event.getAction() == MotionEvent.ACTION_UP) {
+
+        if (action == MotionEvent.ACTION_UP) {
+            mInteractionMode = InteractionMode.NONE;
             mTouchShowOriginal = false;
-            mTouchDownX = 0;
-            mTouchDownY = 0;
-            mTouchX = 0;
-            mTouchY = 0;
+            mTouchDown.x = 0;
+            mTouchDown.y = 0;
+            mTouch.x = 0;
+            mTouch.y = 0;
+            if (MasterImage.getImage().getScaleFactor() <= 1) {
+                MasterImage.getImage().setScaleFactor(1);
+                MasterImage.getImage().resetTranslation();
+            }
         }
         invalidate();
         return true;
     }
 
+    protected boolean enableComparison() {
+        return true;
+    }
+
     // listview stuff
-
-    public HistoryAdapter getHistory() {
-        return mHistoryAdapter;
-    }
-
-    public ArrayAdapter getImageStateAdapter() {
-        return mImageStateAdapter;
-    }
-
-    public void onItemClick(int position) {
-        setImagePreset(new ImagePreset(mHistoryAdapter.getItem(position)), false);
-        // we need a copy from the history
-        mHistoryAdapter.setCurrentPreset(position);
-    }
-
     public void showOriginal(boolean show) {
-        mShowOriginal = show;
         invalidate();
     }
 
-    public float getImageRotation() {
-        return getImagePreset().mGeoData.getRotation();
-    }
-
-    public float getImageRotationZoomFactor() {
-        return getImagePreset().mGeoData.getScaleFactor();
-    }
-
-    public void setImageRotation(float r) {
-        getImagePreset().mGeoData.setRotation(r);
-    }
-
-    public void setImageRotationZoomFactor(float f) {
-        getImagePreset().mGeoData.setScaleFactor(f);
-    }
-
-    public void setImageRotation(float imageRotation,
-            float imageRotationZoomFactor) {
-        float r = getImageRotation();
-        if (imageRotation != r) {
-            invalidate();
-        }
-        setImageRotation(imageRotation);
-        setImageRotationZoomFactor(imageRotationZoomFactor);
-    }
-
-    @Override
-    public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
-        int parameter = progress;
-        if (getCurrentFilter() != null) {
-            int maxp = getCurrentFilter().getMaxParameter();
-            int minp = getCurrentFilter().getMinParameter();
-            parameter = uiToParameter(progress, minp, maxp, arg0.getMax());
-        }
-
-        onNewValue(parameter);
-    }
-
-    @Override
-    public void onStartTrackingTouch(SeekBar arg0) {
-        // TODO Auto-generated method stub
-
-    }
-
-    @Override
-    public void onStopTrackingTouch(SeekBar arg0) {
-        // TODO Auto-generated method stub
-
-    }
-
     @Override
     public boolean onDoubleTap(MotionEvent arg0) {
-        // TODO Auto-generated method stub
-        return false;
+        mZoomIn = !mZoomIn;
+        float scale = 1.0f;
+        if (mZoomIn) {
+            scale = MasterImage.getImage().getMaxScaleFactor();
+        }
+        if (scale != MasterImage.getImage().getScaleFactor()) {
+            MasterImage.getImage().setScaleFactor(scale);
+            invalidate();
+        }
+        return true;
     }
 
     @Override
@@ -800,14 +669,11 @@
 
     @Override
     public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
-        if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX())
-                || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) {
-            if (!mTouchShowOriginal
-                    || (mTouchShowOriginal &&
-                    (System.currentTimeMillis() - mTouchShowOriginalDate
-                    < mTouchShowOriginalDelayMax))) {
-                mActivity.toggleHistoryPanel();
-            }
+        if (mActivity == null) {
+            return false;
+        }
+        if (endEvent.getPointerCount() == 2) {
+            return false;
         }
         return true;
     }
@@ -815,7 +681,6 @@
     @Override
     public void onLongPress(MotionEvent arg0) {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -827,7 +692,6 @@
     @Override
     public void onShowPress(MotionEvent arg0) {
         // TODO Auto-generated method stub
-
     }
 
     @Override
@@ -836,4 +700,70 @@
         return false;
     }
 
+    public boolean useUtilityPanel() {
+        return false;
+    }
+
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        MasterImage img = MasterImage.getImage();
+        float scaleFactor = img.getScaleFactor();
+        Point pos = img.getTranslation();
+
+        scaleFactor = scaleFactor * detector.getScaleFactor();
+        if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
+            scaleFactor = MasterImage.getImage().getMaxScaleFactor();
+        }
+        if (scaleFactor < 0.5) {
+            scaleFactor = 0.5f;
+        }
+        MasterImage.getImage().setScaleFactor(scaleFactor);
+        scaleFactor = img.getScaleFactor();
+        pos = img.getTranslation();
+        float focusx = detector.getFocusX();
+        float focusy = detector.getFocusY();
+        float translateX = (focusx - mStartFocusX) / scaleFactor;
+        float translateY = (focusy - mStartFocusY) / scaleFactor;
+        Point translation = MasterImage.getImage().getTranslation();
+        translation.x = (int) (mOriginalTranslation.x + translateX);
+        translation.y = (int) (mOriginalTranslation.y + translateY);
+        MasterImage.getImage().setTranslation(translation);
+
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        Point pos = MasterImage.getImage().getTranslation();
+        mOriginalTranslation.x = pos.x;
+        mOriginalTranslation.y = pos.y;
+        mOriginalScale = MasterImage.getImage().getScaleFactor();
+        mStartFocusX = detector.getFocusX();
+        mStartFocusY = detector.getFocusY();
+        mInteractionMode = InteractionMode.SCALE;
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        mInteractionMode = InteractionMode.NONE;
+        if (MasterImage.getImage().getScaleFactor() < 1) {
+            MasterImage.getImage().setScaleFactor(1);
+            invalidate();
+        }
+    }
+
+    public boolean didFinishScalingOperation() {
+        if (mFinishedScalingOperation) {
+            mFinishedScalingOperation = false;
+            return true;
+        }
+        return false;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
deleted file mode 100644
index 3d79ae0..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageSlave.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-
-import com.android.gallery3d.filtershow.HistoryAdapter;
-import com.android.gallery3d.filtershow.PanelController;
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
-public class ImageSlave extends ImageShow {
-    private ImageShow mMasterImageShow = null;
-
-    public ImageSlave(Context context) {
-        super(context);
-    }
-
-    public ImageSlave(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ImageShow getMaster() {
-        return mMasterImageShow;
-    }
-
-    public void setMaster(ImageShow master) {
-        mMasterImageShow = master;
-    }
-
-    @Override
-    public HistoryAdapter getHistory() {
-        return mMasterImageShow.getHistory();
-    }
-
-    @Override
-    public void resetImageCaches(ImageShow caller) {
-        mMasterImageShow.resetImageCaches(caller);
-    }
-
-    @Override
-    public ImagePreset getImagePreset() {
-        return mMasterImageShow.getImagePreset();
-    }
-
-    @Override
-    public Rect getDisplayedImageBounds() {
-        return mMasterImageShow.getDisplayedImageBounds();
-    }
-
-    @Override
-    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
-        mMasterImageShow.setImagePreset(preset, addToHistory);
-    }
-
-    @Override
-    public void setCurrentFilter(ImageFilter filter) {
-        mMasterImageShow.setCurrentFilter(filter);
-    }
-
-    @Override
-    public ImageFilter getCurrentFilter() {
-        return mMasterImageShow.getCurrentFilter();
-    }
-
-    @Override
-    public Bitmap getFilteredImage() {
-        return mMasterImageShow.getFilteredImage();
-    }
-
-    @Override
-    public void updateImage() {
-        mMasterImageShow.updateImage();
-    }
-
-    @Override
-    public void updateImagePresets(boolean force) {
-        mMasterImageShow.updateImagePresets(force);
-    }
-
-    @Override
-    public void requestFilteredImages() {
-        mMasterImageShow.requestFilteredImages();
-    }
-
-    @Override
-    public boolean showTitle() {
-        return false;
-    }
-
-    @Override
-    public float getImageRotation() {
-        return mMasterImageShow.getImageRotation();
-    }
-
-    @Override
-    public float getImageRotationZoomFactor() {
-        return mMasterImageShow.getImageRotationZoomFactor();
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-    }
-
-    @Override
-    public void setPanelController(PanelController controller) {
-        mMasterImageShow.setPanelController(controller);
-    }
-
-    @Override
-    public PanelController getPanelController() {
-        return mMasterImageShow.getPanelController();
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java
deleted file mode 100644
index d6d3c86..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallBorder.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.util.AttributeSet;
-
-public class ImageSmallBorder extends ImageSmallFilter {
-
-    // TODO: move this to xml.
-    protected final int mSelectedBackgroundColor = Color.WHITE;
-    protected final int mInnerBorderColor = Color.BLACK;
-    protected final int mInnerBorderWidth = 2;
-    protected final float mImageScaleFactor = 3.5f;
-
-    public ImageSmallBorder(Context context) {
-        super(context);
-    }
-
-    public ImageSmallBorder(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        requestFilteredImages();
-        canvas.drawColor(mBackgroundColor);
-        // TODO: simplify & make faster...
-        RectF border = new RectF(mMargin, 2*mMargin, getWidth() - mMargin - 1, getWidth());
-
-        if (mIsSelected) {
-            mPaint.setColor(mSelectedBackgroundColor);
-            canvas.drawRect(0, mMargin, getWidth(), getWidth() + mMargin, mPaint);
-        }
-
-        mPaint.setColor(mInnerBorderColor);
-        mPaint.setStrokeWidth(mInnerBorderWidth);
-        Path path = new Path();
-        path.addRect(border, Path.Direction.CCW);
-        mPaint.setStyle(Paint.Style.STROKE);
-        canvas.drawPath(path, mPaint);
-        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-        canvas.save();
-        canvas.clipRect(mMargin + 1, 2*mMargin, getWidth() - mMargin - 2, getWidth() - 1,
-                Region.Op.INTERSECT);
-        canvas.translate(mMargin + 1, 2*mMargin + 1);
-        canvas.scale(mImageScaleFactor, mImageScaleFactor);
-        Rect d = new Rect(0, 0, getWidth(), getWidth());
-        drawImage(canvas, getFilteredImage(), d);
-        canvas.restore();
-    }
-
-    @Override
-    public void drawImage(Canvas canvas, Bitmap image, Rect d) {
-        if (image != null) {
-            int iw = image.getWidth();
-            int ih = image.getHeight();
-            Rect s = new Rect(0, 0, iw, iw);
-            canvas.drawBitmap(image, s, d, mPaint);
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
deleted file mode 100644
index 6a79e18..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.gallery3d.filtershow.FilterShowActivity;
-import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
-
-public class ImageSmallFilter extends ImageShow implements View.OnClickListener {
-
-    private static final String LOGTAG = "ImageSmallFilter";
-    private FilterShowActivity mController = null;
-    protected ImageFilter mImageFilter = null;
-    private boolean mShowTitle = true;
-    private boolean mSetBorder = false;
-    protected final Paint mPaint = new Paint();
-    protected boolean mIsSelected = false;
-
-    // TODO: move this to xml.
-    protected static int mMargin = 12;
-    protected static int mTextMargin = 8;
-    protected static int mBackgroundColor = Color.BLUE;
-    protected final int mSelectedBackgroundColor = Color.WHITE;
-    protected final int mTextColor = Color.WHITE;
-    private ImageSmallFilter mNullFilter;
-
-    public static void setMargin(int value) {
-        mMargin = value;
-    }
-
-    public static void setTextMargin(int value) {
-        mTextMargin = value;
-    }
-
-    public static void setDefaultBackgroundColor(int value) {
-        mBackgroundColor = value;
-    }
-
-    public ImageSmallFilter(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setOnClickListener(this);
-    }
-
-    public ImageSmallFilter(Context context) {
-        super(context);
-        setOnClickListener(this);
-    }
-
-    public void setImageFilter(ImageFilter filter) {
-        mImageFilter = filter;
-        mImagePreset = new ImagePreset();
-        mImagePreset.setName(filter.getName());
-        filter.setImagePreset(mImagePreset);
-        mImagePreset.add(mImageFilter);
-    }
-
-    @Override
-    public void setSelected(boolean value) {
-        if (mIsSelected != value) {
-            invalidate();
-        }
-        mIsSelected = value;
-    }
-
-    public void setBorder(boolean value) {
-        mSetBorder = value;
-    }
-
-    public void setController(FilterShowActivity activity) {
-        mController = activity;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
-        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
-        int h = mTextSize + mTextPadding;
-        setMeasuredDimension(parentHeight - h, parentHeight);
-    }
-
-    /**
-     * Setting the nullFilter implies that the behavior of the button is toggle
-     *
-     * @param nullFilter
-     */
-    public void setNulfilter(ImageSmallFilter nullFilter) {
-        mNullFilter = nullFilter;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (mController != null) {
-            if (mImageFilter != null) {
-                if (mIsSelected && mNullFilter != null) {
-                    mNullFilter.onClick(v);
-                }
-                else {
-                    mController.useImageFilter(this, mImageFilter, mSetBorder);
-                }
-            } else if (mImagePreset != null) {
-                if (mIsSelected && mNullFilter != null) {
-                    mNullFilter.onClick(v);
-                }
-                else {
-                    mController.useImagePreset(this, mImagePreset);
-                }
-            }
-        }
-    }
-
-    @Override
-    public boolean updateGeometryFlags() {
-        // We don't want to warn listeners here that the image size has changed, because
-        // we'll be working with the small image...
-        return false;
-    }
-
-    public void setShowTitle(boolean value) {
-        mShowTitle = value;
-        invalidate();
-    }
-
-    @Override
-    public boolean showTitle() {
-        return mShowTitle;
-    }
-
-    @Override
-    public boolean showControls() {
-        return false;
-    }
-
-    @Override
-    public boolean showHires() {
-        return false;
-    }
-
-    @Override
-    public ImagePreset getImagePreset() {
-        return mImagePreset;
-    }
-
-    @Override
-    public void updateImagePresets(boolean force) {
-        ImagePreset preset = getImagePreset();
-        if (preset == null) {
-            return;
-        }
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        requestFilteredImages();
-        canvas.drawColor(mBackgroundColor);
-        float textWidth = mPaint.measureText(mImageFilter.getName());
-        int h = mTextSize + 2 * mTextPadding;
-        int x = (int) ((getWidth() - textWidth) / 2);
-        int y = getHeight();
-        if (mIsSelected) {
-            mPaint.setColor(mSelectedBackgroundColor);
-            canvas.drawRect(0, mMargin, getWidth(), getWidth() + mMargin, mPaint);
-        }
-        Rect destination = new Rect(mMargin, 2*mMargin, getWidth() - mMargin, getWidth());
-        drawImage(canvas, getFilteredImage(), destination);
-        mPaint.setTextSize(mTextSize);
-        mPaint.setColor(mTextColor);
-        canvas.drawText(mImageFilter.getName(), x, y - mTextMargin, mPaint);
-    }
-
-    public void drawImage(Canvas canvas, Bitmap image, Rect destination) {
-        if (image != null) {
-            int iw = image.getWidth();
-            int ih = image.getHeight();
-            int x = 0;
-            int y = 0;
-            int size = 0;
-            Rect source = null;
-            if (iw > ih) {
-                size = ih;
-                x = (int) ((iw - size) / 2.0f);
-                y = 0;
-            } else {
-                size = iw;
-                x = 0;
-                y = (int) ((ih - size) / 2.0f);
-            }
-            source = new Rect(x, y, x + size, y + size);
-            canvas.drawBitmap(image, source, destination, mPaint);
-        }
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
index 57a22aa..5f906ea 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java
@@ -19,18 +19,18 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Path;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
 
 public class ImageStraighten extends ImageGeometry {
 
     private float mBaseAngle = 0;
     private float mAngle = 0;
+    private EditorStraighten mEditorStraighten;
 
     private static final String LOGTAG = "ImageStraighten";
     private static final Paint gPaint = new Paint();
@@ -92,9 +92,6 @@
     @Override
     public void onNewValue(int value) {
         setLocalStraighten(GeometryMath.clamp(value, MIN_STRAIGHTEN_ANGLE, MAX_STRAIGHTEN_ANGLE));
-        if (getPanelController() != null) {
-            getPanelController().onNewValue((int) getLocalStraighten());
-        }
         invalidate();
     }
 
@@ -105,12 +102,10 @@
 
     @Override
     protected void drawShape(Canvas canvas, Bitmap image) {
-        drawTransformed(canvas, image, gPaint);
+        float [] o = {0, 0};
+        RectF bounds = drawTransformed(canvas, image, gPaint, o);
 
         // Draw the grid
-        RectF bounds = straightenBounds();
-        Path path = new Path();
-        path.addRect(bounds, Path.Direction.CCW);
         gPaint.setARGB(255, 255, 255, 255);
         gPaint.setStrokeWidth(3);
         gPaint.setStyle(Paint.Style.FILL_AND_STROKE);
@@ -121,7 +116,7 @@
 
         if (mMode == MODES.MOVE) {
             canvas.save();
-            canvas.clipPath(path);
+            canvas.clipRect(bounds);
 
             int n = 16;
             float step = dWidth / n;
@@ -136,4 +131,8 @@
         }
     }
 
+    public void setEditor(EditorStraighten editorStraighten) {
+        mEditorStraighten = editorStraighten;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
index 31bfe43..25a0a90 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageTinyPlanet.java
@@ -17,12 +17,20 @@
 package com.android.gallery3d.filtershow.imageshow;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
 
-import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+import com.android.gallery3d.filtershow.filters.FilterTinyPlanetRepresentation;
 
-public class ImageTinyPlanet extends ImageSlave {
+public class ImageTinyPlanet extends ImageShow {
+    private static final String LOGTAG = "ImageTinyPlanet";
 
     private float mTouchCenterX = 0;
     private float mTouchCenterY = 0;
@@ -31,13 +39,49 @@
     private float mCenterX = 0;
     private float mCenterY = 0;
     private float mStartAngle = 0;
+    private FilterTinyPlanetRepresentation mTinyPlanetRep;
+    private EditorTinyPlanet mEditorTinyPlanet;
+    private ScaleGestureDetector mScaleGestureDetector = null;
+    boolean mInScale = false;
+    RectF mDestRect = new RectF();
+
+    OnScaleGestureListener mScaleGestureListener = new OnScaleGestureListener() {
+        private float mScale = 100;
+        @Override
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            mInScale = false;
+        }
+
+        @Override
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            mInScale = true;
+            mScale = mTinyPlanetRep.getValue();
+            return true;
+        }
+
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            int value = mTinyPlanetRep.getValue();
+            mScale *= detector.getScaleFactor();
+            value = (int) (mScale);
+            value = Math.min(mTinyPlanetRep.getMaximum(), value);
+            value = Math.max(mTinyPlanetRep.getMinimum(), value);
+            mTinyPlanetRep.setValue(value);
+            invalidate();
+            mEditorTinyPlanet.commitLocalRepresentation();
+            mEditorTinyPlanet.updateUI();
+            return true;
+        }
+    };
 
     public ImageTinyPlanet(Context context) {
         super(context);
+        mScaleGestureDetector = new ScaleGestureDetector(context, mScaleGestureListener);
     }
 
     public ImageTinyPlanet(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mScaleGestureDetector = new ScaleGestureDetector(context,mScaleGestureListener );
     }
 
     protected static float angleFor(float dx, float dy) {
@@ -60,26 +104,71 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        ImageFilterTinyPlanet filter = (ImageFilterTinyPlanet) getCurrentFilter();
         float x = event.getX();
         float y = event.getY();
         mCurrentX = x;
         mCurrentY = y;
         mCenterX = getWidth() / 2;
         mCenterY = getHeight() / 2;
+        mScaleGestureDetector.onTouchEvent(event);
+        if (mInScale) {
+            return true;
+        }
         switch (event.getActionMasked()) {
             case (MotionEvent.ACTION_DOWN):
                 mTouchCenterX = x;
                 mTouchCenterY = y;
-                mStartAngle = filter.getAngle();
+                mStartAngle = mTinyPlanetRep.getAngle();
                 break;
-            case (MotionEvent.ACTION_UP):
+
             case (MotionEvent.ACTION_MOVE):
-                filter.setAngle(mStartAngle + getCurrentTouchAngle());
+                mTinyPlanetRep.setAngle(mStartAngle + getCurrentTouchAngle());
                 break;
         }
-        resetImageCaches(this);
         invalidate();
+        mEditorTinyPlanet.commitLocalRepresentation();
         return true;
     }
+
+    public void setRepresentation(FilterTinyPlanetRepresentation tinyPlanetRep) {
+        mTinyPlanetRep = tinyPlanetRep;
+    }
+
+    public void setEditor(BasicEditor editorTinyPlanet) {
+        mEditorTinyPlanet = (EditorTinyPlanet) editorTinyPlanet;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        Bitmap bitmap = MasterImage.getImage().getHighresImage();
+        if (bitmap == null) {
+            bitmap = MasterImage.getImage().getFilteredImage();
+        }
+
+        if (bitmap != null) {
+            display(canvas, bitmap);
+        }
+    }
+
+    private void display(Canvas canvas, Bitmap bitmap) {
+        float sw = canvas.getWidth();
+        float sh = canvas.getHeight();
+        float iw = bitmap.getWidth();
+        float ih = bitmap.getHeight();
+        float nsw = sw;
+        float nsh = sh;
+
+        if (sw * ih > sh * iw) {
+            nsw = sh * iw / ih;
+        } else {
+            nsh = sw * ih / iw;
+        }
+
+        mDestRect.left = (sw - nsw) / 2;
+        mDestRect.top = (sh - nsh) / 2;
+        mDestRect.right = sw - mDestRect.left;
+        mDestRect.bottom = sh - mDestRect.top;
+
+        canvas.drawBitmap(bitmap, null, mDestRect, mPaint);
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
new file mode 100644
index 0000000..7ce9e51
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageVignette.java
@@ -0,0 +1,165 @@
+/*
+ * 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.gallery3d.filtershow.imageshow;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.gallery3d.filtershow.editors.EditorVignette;
+import com.android.gallery3d.filtershow.filters.FilterVignetteRepresentation;
+
+public class ImageVignette extends ImageShow {
+    private static final String LOGTAG = "ImageVignette";
+
+    private FilterVignetteRepresentation mVignetteRep;
+    private EditorVignette mEditorVignette;
+
+    private int mActiveHandle = -1;
+
+    EclipseControl mElipse;
+
+    public ImageVignette(Context context) {
+        super(context);
+        mElipse = new EclipseControl(context);
+    }
+
+    public ImageVignette(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mElipse = new EclipseControl(context);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int mask = event.getActionMasked();
+        if (mActiveHandle == -1) {
+            if (MotionEvent.ACTION_DOWN != mask) {
+                return super.onTouchEvent(event);
+            }
+            if (event.getPointerCount() == 1) {
+                mActiveHandle = mElipse.getCloseHandle(event.getX(), event.getY());
+            }
+            if (mActiveHandle == -1) {
+                return super.onTouchEvent(event);
+            }
+        } else {
+            switch (mask) {
+                case MotionEvent.ACTION_UP:
+                    mActiveHandle = -1;
+                    break;
+                case MotionEvent.ACTION_DOWN:
+                    break;
+            }
+        }
+        float x = event.getX();
+        float y = event.getY();
+
+        mElipse.setScrToImageMatrix(getScreenToImageMatrix(true));
+
+        boolean didComputeEllipses = false;
+        switch (mask) {
+            case (MotionEvent.ACTION_DOWN):
+                mElipse.actionDown(x, y, mVignetteRep);
+                break;
+            case (MotionEvent.ACTION_UP):
+            case (MotionEvent.ACTION_MOVE):
+                mElipse.actionMove(mActiveHandle, x, y, mVignetteRep);
+                setRepresentation(mVignetteRep);
+                didComputeEllipses = true;
+                break;
+        }
+        if (!didComputeEllipses) {
+            computeEllipses();
+        }
+        invalidate();
+        return true;
+    }
+
+    public void setRepresentation(FilterVignetteRepresentation vignetteRep) {
+        mVignetteRep = vignetteRep;
+        computeEllipses();
+    }
+
+    public void computeEllipses() {
+        if (mVignetteRep == null) {
+            return;
+        }
+        Matrix toImg = getScreenToImageMatrix(false);
+        Matrix toScr = new Matrix();
+        toImg.invert(toScr);
+
+        float[] c = new float[] {
+                mVignetteRep.getCenterX(), mVignetteRep.getCenterY() };
+        if (Float.isNaN(c[0])) {
+            float cx = mImageLoader.getOriginalBounds().width() / 2;
+            float cy = mImageLoader.getOriginalBounds().height() / 2;
+            float rx = Math.min(cx, cy) * .8f;
+            float ry = rx;
+            mVignetteRep.setCenter(cx, cy);
+            mVignetteRep.setRadius(rx, ry);
+
+            c[0] = cx;
+            c[1] = cy;
+            toScr.mapPoints(c);
+            if (getWidth() != 0) {
+                mElipse.setCenter(c[0], c[1]);
+                mElipse.setRadius(c[0] * 0.8f, c[1] * 0.8f);
+            }
+        } else {
+
+            toScr.mapPoints(c);
+
+            mElipse.setCenter(c[0], c[1]);
+            mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
+                    toScr.mapRadius(mVignetteRep.getRadiusY()));
+        }
+        mEditorVignette.commitLocalRepresentation();
+    }
+
+    public void setEditor(EditorVignette editorVignette) {
+        mEditorVignette = editorVignette;
+    }
+
+    @Override
+    public void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w,  h, oldw, oldh);
+        computeEllipses();
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (mVignetteRep == null) {
+            return;
+        }
+        Matrix toImg = getScreenToImageMatrix(false);
+        Matrix toScr = new Matrix();
+        toImg.invert(toScr);
+        float[] c = new float[] {
+                mVignetteRep.getCenterX(), mVignetteRep.getCenterY() };
+        toScr.mapPoints(c);
+        mElipse.setCenter(c[0], c[1]);
+        mElipse.setRadius(toScr.mapRadius(mVignetteRep.getRadiusX()),
+                toScr.mapRadius(mVignetteRep.getRadiusY()));
+
+        mElipse.draw(canvas);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java b/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java
deleted file mode 100644
index a332fa7..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-
-/**
- * TODO: Insert description here. (generated by hoford)
- */
-public class ImageWithIcon extends ImageSmallFilter {
-    /**
-     * @param context
-     */
-    public ImageWithIcon(Context context) {
-        super(context);
-        // TODO(hoford): Auto-generated constructor stub
-    }
-
-    private Bitmap bitmap;
-
-    public void setIcon(Bitmap bitmap){
-        this.bitmap = bitmap;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (bitmap != null) {
-            Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth());
-            drawImage(canvas, bitmap, d);
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
deleted file mode 100644
index c7586fe..0000000
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.gallery3d.filtershow.cache.ImageLoader;
-
-public class ImageZoom extends ImageSlave {
-    private static final String LOGTAG = "ImageZoom";
-    private boolean mTouchDown = false;
-    private boolean mZoomedIn = false;
-    private Rect mZoomBounds = null;
-    private static float mMaxSize = 512;
-
-    public ImageZoom(Context context) {
-        super(context);
-    }
-
-    public ImageZoom(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public static void setZoomedSize(float size) {
-        mMaxSize = size;
-    }
-
-    @Override
-    public void resetParameter() {
-        super.resetParameter();
-        mZoomedIn = false;
-        mTouchDown = false;
-    }
-
-    @Override
-    public void onTouchDown(float x, float y) {
-        super.onTouchDown(x, y);
-        if (mZoomedIn || mTouchDown) {
-            return;
-        }
-        mTouchDown = true;
-        GeometryMetadata geo = getImagePreset().mGeoData;
-        Matrix originalToScreen = geo.getOriginalToScreen(true,
-                mImageLoader.getOriginalBounds().width(),
-                mImageLoader.getOriginalBounds().height(),
-                getWidth(), getHeight());
-        float[] point = new float[2];
-        point[0] = x;
-        point[1] = y;
-        Matrix inverse = new Matrix();
-        originalToScreen.invert(inverse);
-        inverse.mapPoints(point);
-
-        float ratio = (float) getWidth() / (float) getHeight();
-        float mh = mMaxSize;
-        float mw = ratio * mh;
-        RectF zoomRect = new RectF(mTouchX - mw, mTouchY - mh, mTouchX + mw, mTouchY + mw);
-        inverse.mapRect(zoomRect);
-        zoomRect.set(zoomRect.centerX() - mw, zoomRect.centerY() - mh,
-                zoomRect.centerX() + mw, zoomRect.centerY() + mh);
-        mZoomBounds = new Rect((int) zoomRect.left, (int) zoomRect.top,
-                (int) zoomRect.right, (int) zoomRect.bottom);
-        invalidate();
-    }
-
-    @Override
-    public void onTouchUp() {
-        mTouchDown = false;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        drawBackground(canvas);
-
-        Bitmap filteredImage = null;
-        if ((mZoomedIn || mTouchDown) && mImageLoader != null) {
-            filteredImage = mImageLoader.getScaleOneImageForPreset(this, getImagePreset(),
-                    mZoomBounds, false);
-        } else {
-            requestFilteredImages();
-            filteredImage = getFilteredImage();
-        }
-        canvas.save();
-        if (mZoomedIn || mTouchDown) {
-            int orientation = ImageLoader.getZoomOrientation();
-            switch (orientation) {
-                case ImageLoader.ORI_ROTATE_90: {
-                    canvas.rotate(90, getWidth() / 2, getHeight() / 2);
-                    break;
-                }
-                case ImageLoader.ORI_ROTATE_270: {
-                    canvas.rotate(270, getWidth() / 2, getHeight() / 2);
-                    break;
-                }
-                case ImageLoader.ORI_TRANSPOSE: {
-                    canvas.rotate(90, getWidth() / 2, getHeight() / 2);
-                    canvas.scale(1,  -1);
-                    break;
-                }
-                case ImageLoader.ORI_TRANSVERSE: {
-                    canvas.rotate(270, getWidth() / 2, getHeight() / 2);
-                    canvas.scale(1,  -1);
-                    break;
-                }
-            }
-        }
-        drawImage(canvas, filteredImage);
-        canvas.restore();
-
-        if (showControls()) {
-            mSliderController.onDraw(canvas);
-        }
-
-        drawToast(canvas);
-    }
-
-    @Override
-    public boolean onDoubleTap(MotionEvent event) {
-
-        if (!mZoomedIn) {
-            onTouchDown(event.getX(), event.getY());
-        } else {
-            onTouchUp();
-        }
-        mZoomedIn = !mZoomedIn;
-        invalidate();
-        return false;
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
new file mode 100644
index 0000000..a0b59c0
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -0,0 +1,526 @@
+/*
+ * 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.gallery3d.filtershow.imageshow;
+
+import android.graphics.*;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.HistoryAdapter;
+import com.android.gallery3d.filtershow.cache.*;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+
+import java.util.Vector;
+
+public class MasterImage implements RenderingRequestCaller {
+
+    private static final String LOGTAG = "MasterImage";
+    private boolean DEBUG  = false;
+    private static final boolean DISABLEZOOM = true;
+    private static MasterImage sMasterImage = null;
+    private static int sIconSeedSize = 128;
+    private static float sHistoryPreviewSize = 128.0f;
+
+    private boolean mSupportsHighRes = false;
+
+    private ImageFilter mCurrentFilter = null;
+    private ImagePreset mPreset = null;
+    private ImagePreset mGeometryOnlyPreset = null;
+    private ImagePreset mFiltersOnlyPreset = null;
+
+    private TripleBufferBitmap mFilteredPreview = new TripleBufferBitmap();
+
+    private Bitmap mGeometryOnlyBitmap = null;
+    private Bitmap mFiltersOnlyBitmap = null;
+    private Bitmap mPartialBitmap = null;
+    private Bitmap mHighresBitmap = null;
+
+    private ImageLoader mLoader = null;
+    private HistoryAdapter mHistory = null;
+    private StateAdapter mState = null;
+
+    private FilterShowActivity mActivity = null;
+
+    private Vector<ImageShow> mObservers = new Vector<ImageShow>();
+    private FilterRepresentation mCurrentFilterRepresentation;
+    private Vector<GeometryListener> mGeometryListeners = new Vector<GeometryListener>();
+
+    private GeometryMetadata mPreviousGeometry = null;
+
+    private float mScaleFactor = 1.0f;
+    private float mMaxScaleFactor = 3.0f; // TODO: base this on the current view / image
+    private Point mTranslation = new Point();
+    private Point mOriginalTranslation = new Point();
+
+    private Point mImageShowSize = new Point();
+
+    private boolean mShowsOriginal;
+
+    final private static int NEW_GEOMETRY = 1;
+
+    private final Handler mHandler = new Handler() {
+            @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case NEW_GEOMETRY: {
+                hasNewGeometry();
+                break;
+            }
+            }
+        }
+    };
+
+    private MasterImage() {
+    }
+
+    // TODO: remove singleton
+    public static void setMaster(MasterImage master) {
+        sMasterImage = master;
+    }
+
+    public static MasterImage getImage() {
+        if (sMasterImage == null) {
+            sMasterImage = new MasterImage();
+        }
+        return sMasterImage;
+    }
+
+    public void setSupportsHighRes(boolean value) {
+        mSupportsHighRes = value;
+    }
+
+    public static void setIconSeedSize(int iconSeedSize) {
+        sIconSeedSize = iconSeedSize;
+    }
+
+    public void addObserver(ImageShow observer) {
+        if (mObservers.contains(observer)) {
+            return;
+        }
+        mObservers.add(observer);
+    }
+
+    public void setActivity(FilterShowActivity activity) {
+        mActivity = activity;
+    }
+
+    public ImageLoader getLoader() {
+        return mLoader;
+    }
+
+    public synchronized ImagePreset getPreset() {
+        return mPreset;
+    }
+
+    public synchronized ImagePreset getGeometryPreset() {
+        return mGeometryOnlyPreset;
+    }
+
+    public synchronized ImagePreset getFiltersOnlyPreset() {
+        return mFiltersOnlyPreset;
+    }
+
+    public synchronized void setPreset(ImagePreset preset, boolean addToHistory) {
+        mPreset = preset;
+        mPreset.setImageLoader(mLoader);
+        setGeometry();
+        mPreset.fillImageStateAdapter(mState);
+        if (addToHistory) {
+            mHistory.addHistoryItem(mPreset);
+        }
+        updatePresets(true);
+        GeometryMetadata geo = mPreset.mGeoData;
+        if (!geo.equals(mPreviousGeometry)) {
+            notifyGeometryChange();
+        }
+        mPreviousGeometry = new GeometryMetadata(geo);
+    }
+
+    private void renderHistoryPreview() {
+        ImagePreset historyPreset = mPreset;
+        if (historyPreset != null) {
+            Bitmap preview = mLoader.getOriginalBitmapSmall();
+            if (preview != null) {
+                float s = Math.min(preview.getWidth(), preview.getHeight());
+                float f = sHistoryPreviewSize / s;
+                int w = (int) (preview.getWidth() * f);
+                int h = (int) (preview.getHeight() * f);
+                Bitmap historyPreview = Bitmap.createScaledBitmap(preview, w, h, true);
+                historyPreset.setPreviewImage(historyPreview);
+                RenderingRequest.post(historyPreview,
+                        historyPreset, RenderingRequest.ICON_RENDERING, this);
+            }
+        }
+    }
+
+    private void setGeometry() {
+        Bitmap image = mLoader.getOriginalBitmapLarge();
+        if (image == null) {
+            return;
+        }
+        float w = image.getWidth();
+        float h = image.getHeight();
+        GeometryMetadata geo = mPreset.mGeoData;
+        RectF pb = geo.getPhotoBounds();
+        if (w == pb.width() && h == pb.height()) {
+            return;
+        }
+        RectF r = new RectF(0, 0, w, h);
+        geo.setPhotoBounds(r);
+        geo.setCropBounds(r);
+    }
+
+    public void onHistoryItemClick(int position) {
+        setPreset(new ImagePreset(mHistory.getItem(position)), false);
+        // We need a copy from the history
+        mHistory.setCurrentPreset(position);
+    }
+
+    public HistoryAdapter getHistory() {
+        return mHistory;
+    }
+
+    public StateAdapter getState() {
+        return mState;
+    }
+
+    public void setHistoryAdapter(HistoryAdapter adapter) {
+        mHistory = adapter;
+    }
+
+    public void setStateAdapter(StateAdapter adapter) {
+        mState = adapter;
+    }
+
+    public void setImageLoader(ImageLoader loader) {
+        mLoader = loader;
+    }
+
+    public ImageLoader getImageLoader() {
+        return mLoader;
+    }
+
+    public void setCurrentFilter(ImageFilter filter) {
+        mCurrentFilter = filter;
+    }
+
+    public ImageFilter getCurrentFilter() {
+        return mCurrentFilter;
+    }
+
+    public synchronized boolean hasModifications() {
+        if (mPreset == null) {
+            return false;
+        }
+        return mPreset.hasModifications();
+    }
+
+    public TripleBufferBitmap getDoubleBuffer() {
+        return mFilteredPreview;
+    }
+
+    public void setOriginalGeometry(Bitmap originalBitmapLarge) {
+        GeometryMetadata geo = getPreset().mGeoData;
+        float w = originalBitmapLarge.getWidth();
+        float h = originalBitmapLarge.getHeight();
+        RectF r = new RectF(0, 0, w, h);
+        geo.setPhotoBounds(r);
+        geo.setCropBounds(r);
+        getPreset().setGeometry(geo);
+    }
+
+    public Bitmap getFilteredImage() {
+        return mFilteredPreview.getConsumer();
+    }
+
+    public Bitmap getFiltersOnlyImage() {
+        return mFiltersOnlyBitmap;
+    }
+
+    public Bitmap getGeometryOnlyImage() {
+        return mGeometryOnlyBitmap;
+    }
+
+    public Bitmap getPartialImage() {
+        return mPartialBitmap;
+    }
+
+    public Bitmap getHighresImage() {
+        return mHighresBitmap;
+    }
+
+    public void notifyObservers() {
+        for (ImageShow observer : mObservers) {
+            observer.invalidate();
+        }
+    }
+
+    public void updatePresets(boolean force) {
+        if (force || mGeometryOnlyPreset == null) {
+            ImagePreset newPreset = new ImagePreset(mPreset);
+            newPreset.setDoApplyFilters(false);
+            newPreset.setDoApplyGeometry(true);
+            if (force || mGeometryOnlyPreset == null
+                    || !newPreset.same(mGeometryOnlyPreset)) {
+                mGeometryOnlyPreset = newPreset;
+                RenderingRequest.post(mLoader.getOriginalBitmapLarge(),
+                        mGeometryOnlyPreset, RenderingRequest.GEOMETRY_RENDERING, this);
+            }
+        }
+        if (force || mFiltersOnlyPreset == null) {
+            ImagePreset newPreset = new ImagePreset(mPreset);
+            newPreset.setDoApplyFilters(true);
+            newPreset.setDoApplyGeometry(false);
+            if (force || mFiltersOnlyPreset == null
+                    || !newPreset.same(mFiltersOnlyPreset)) {
+                mFiltersOnlyPreset = newPreset;
+                RenderingRequest.post(mLoader.getOriginalBitmapLarge(),
+                        mFiltersOnlyPreset, RenderingRequest.FILTERS_RENDERING, this);
+            }
+        }
+        invalidatePreview();
+        mActivity.enableSave(hasModifications());
+    }
+
+    public FilterRepresentation getCurrentFilterRepresentation() {
+        return mCurrentFilterRepresentation;
+    }
+
+    public void setCurrentFilterRepresentation(FilterRepresentation currentFilterRepresentation) {
+        mCurrentFilterRepresentation = currentFilterRepresentation;
+    }
+
+    public void invalidateFiltersOnly() {
+        mFiltersOnlyPreset = null;
+        updatePresets(false);
+    }
+
+    public void invalidatePartialPreview() {
+        if (mPartialBitmap != null) {
+            mPartialBitmap = null;
+            notifyObservers();
+        }
+    }
+
+    public void invalidateHighresPreview() {
+        if (mHighresBitmap != null) {
+            mHighresBitmap = null;
+            notifyObservers();
+        }
+    }
+
+    public void invalidatePreview() {
+        mFilteredPreview.invalidate();
+        invalidatePartialPreview();
+        invalidateHighresPreview();
+        needsUpdatePartialPreview();
+        needsUpdateHighResPreview();
+        FilteringPipeline.getPipeline().updatePreviewBuffer();
+    }
+
+    public void setImageShowSize(int w, int h) {
+        if (mImageShowSize.x != w || mImageShowSize.y != h) {
+            mImageShowSize.set(w, h);
+            needsUpdatePartialPreview();
+            needsUpdateHighResPreview();
+        }
+    }
+
+    private Matrix getImageToScreenMatrix(boolean reflectRotation) {
+        GeometryMetadata geo = mPreset.mGeoData;
+        if (geo == null || mLoader == null
+                || mLoader.getOriginalBounds() == null
+                || mImageShowSize.x == 0) {
+            return new Matrix();
+        }
+        Matrix m = geo.getOriginalToScreen(reflectRotation,
+                mLoader.getOriginalBounds().width(),
+                mLoader.getOriginalBounds().height(), mImageShowSize.x, mImageShowSize.y);
+        Point translate = getTranslation();
+        float scaleFactor = getScaleFactor();
+        m.postTranslate(translate.x, translate.y);
+        m.postScale(scaleFactor, scaleFactor, mImageShowSize.x/2.0f, mImageShowSize.y/2.0f);
+        return m;
+    }
+
+    private Matrix getScreenToImageMatrix(boolean reflectRotation) {
+        Matrix m = getImageToScreenMatrix(reflectRotation);
+        Matrix invert = new Matrix();
+        m.invert(invert);
+        return invert;
+    }
+
+    public void needsUpdateHighResPreview() {
+        if (!mSupportsHighRes) {
+            return;
+        }
+        RenderingRequest.post(null, mPreset, RenderingRequest.HIGHRES_RENDERING, this);
+        invalidateHighresPreview();
+    }
+
+    public void needsUpdatePartialPreview() {
+        if (!mPreset.canDoPartialRendering()) {
+            invalidatePartialPreview();
+            return;
+        }
+        Matrix m = getScreenToImageMatrix(true);
+        RectF r = new RectF(0, 0, mImageShowSize.x, mImageShowSize.y);
+        RectF dest = new RectF();
+        m.mapRect(dest, r);
+        Rect bounds = new Rect();
+        dest.roundOut(bounds);
+        RenderingRequest.post(null, mPreset, RenderingRequest.PARTIAL_RENDERING,
+                this, bounds, new Rect(0, 0, mImageShowSize.x, mImageShowSize.y));
+        invalidatePartialPreview();
+    }
+
+    @Override
+    public void available(RenderingRequest request) {
+        if (request.getBitmap() == null) {
+            return;
+        }
+        if (request.getType() == RenderingRequest.GEOMETRY_RENDERING) {
+            mGeometryOnlyBitmap = request.getBitmap();
+        }
+        if (request.getType() == RenderingRequest.FILTERS_RENDERING) {
+            mFiltersOnlyBitmap = request.getBitmap();
+        }
+        if (request.getType() == RenderingRequest.PARTIAL_RENDERING
+                && request.getScaleFactor() == getScaleFactor()) {
+            mPartialBitmap = request.getBitmap();
+            notifyObservers();
+        }
+        if (request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+            mHighresBitmap = request.getBitmap();
+            notifyObservers();
+        }
+
+        if (request.getType() == RenderingRequest.ICON_RENDERING) {
+            // History preview images
+            ImagePreset preset = request.getOriginalImagePreset();
+            preset.setPreviewImage(request.getBitmap());
+            mHistory.notifyDataSetChanged();
+        }
+    }
+
+    public static void reset() {
+        sMasterImage = null;
+    }
+
+    public void addGeometryListener(GeometryListener listener) {
+        mGeometryListeners.add(listener);
+    }
+
+    public void notifyGeometryChange() {
+        if (mHandler.hasMessages(NEW_GEOMETRY)) {
+            return;
+        }
+        mHandler.sendEmptyMessage(NEW_GEOMETRY);
+    }
+
+    public void hasNewGeometry() {
+        updatePresets(true);
+        for (GeometryListener listener : mGeometryListeners) {
+            listener.geometryChanged();
+        }
+    }
+
+
+    public float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    public void setScaleFactor(float scaleFactor) {
+        if (DISABLEZOOM) {
+            return;
+        }
+        if (scaleFactor == mScaleFactor) {
+            return;
+        }
+        mScaleFactor = scaleFactor;
+        invalidatePartialPreview();
+    }
+
+    public Point getTranslation() {
+        return mTranslation;
+    }
+
+    public void setTranslation(Point translation) {
+        if (DISABLEZOOM) {
+            mTranslation.x = 0;
+            mTranslation.y = 0;
+            return;
+        }
+        mTranslation.x = translation.x;
+        mTranslation.y = translation.y;
+        needsUpdatePartialPreview();
+    }
+
+    public Point getOriginalTranslation() {
+        return mOriginalTranslation;
+    }
+
+    public void setOriginalTranslation(Point originalTranslation) {
+        if (DISABLEZOOM) {
+            return;
+        }
+        mOriginalTranslation.x = originalTranslation.x;
+        mOriginalTranslation.y = originalTranslation.y;
+    }
+
+    public void resetTranslation() {
+        mTranslation.x = 0;
+        mTranslation.y = 0;
+        needsUpdatePartialPreview();
+    }
+
+    public Bitmap getThumbnailBitmap() {
+        return mLoader.getOriginalBitmapSmall();
+    }
+
+    public Bitmap getLargeThumbnailBitmap() {
+        return mLoader.getOriginalBitmapLarge();
+    }
+
+    public float getMaxScaleFactor() {
+        if (DISABLEZOOM) {
+            return 1;
+        }
+        return mMaxScaleFactor;
+    }
+
+    public void setMaxScaleFactor(float maxScaleFactor) {
+        mMaxScaleFactor = maxScaleFactor;
+    }
+
+    public boolean supportsHighRes() {
+        return mSupportsHighRes;
+    }
+
+    public void setShowsOriginal(boolean value) {
+        mShowsOriginal = value;
+        notifyObservers();
+    }
+
+    public boolean showsOriginal() {
+        return mShowsOriginal;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/Oval.java b/src/com/android/gallery3d/filtershow/imageshow/Oval.java
new file mode 100644
index 0000000..28f278f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/imageshow/Oval.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.imageshow;
+
+public interface Oval {
+    void setCenter(float x, float y);
+    void setRadius(float w, float h);
+    float getCenterX();
+    float getCenterY();
+    float getRadiusX();
+    float getRadiusY();
+    void setRadiusY(float y);
+    void setRadiusX(float x);
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java
new file mode 100644
index 0000000..c454c1a
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java
@@ -0,0 +1,137 @@
+/*
+ * 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.gallery3d.filtershow.presets;
+
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.util.Log;
+
+import com.android.gallery3d.filtershow.cache.CachingPipeline;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.ImageFilter;
+
+import java.lang.ref.WeakReference;
+import java.util.HashMap;
+
+public class FilterEnvironment {
+    private static final String LOGTAG = "FilterEnvironment";
+    private ImagePreset mImagePreset;
+    private float mScaleFactor;
+    private int mQuality;
+    private FiltersManager mFiltersManager;
+    private CachingPipeline mCachingPipeline;
+    private volatile boolean mStop = false;
+
+    public synchronized boolean needsStop() {
+        return mStop;
+    }
+
+    public synchronized void setStop(boolean stop) {
+        this.mStop = stop;
+    }
+
+    private HashMap<Long, WeakReference<Bitmap>>
+            bitmapCach = new HashMap<Long, WeakReference<Bitmap>>();
+
+    public void cache(Bitmap bitmap) {
+        if (bitmap == null) {
+            return;
+        }
+        Long key = calcKey(bitmap.getWidth(), bitmap.getHeight());
+        bitmapCach.put(key, new WeakReference<Bitmap>(bitmap));
+    }
+
+    public Bitmap getBitmap(int w, int h) {
+        Long key = calcKey(w, h);
+        WeakReference<Bitmap> ref = bitmapCach.remove(key);
+        Bitmap bitmap = null;
+        if (ref != null) {
+            bitmap = ref.get();
+        }
+        if (bitmap == null) {
+            bitmap = Bitmap.createBitmap(
+                    w, h, Bitmap.Config.ARGB_8888);
+        }
+        return bitmap;
+    }
+
+    private Long calcKey(long w, long h) {
+        return (w << 32) | (h << 32);
+    }
+
+    public void setImagePreset(ImagePreset imagePreset) {
+        mImagePreset = imagePreset;
+    }
+
+    public ImagePreset getImagePreset() {
+        return mImagePreset;
+    }
+
+    public void setScaleFactor(float scaleFactor) {
+        mScaleFactor = scaleFactor;
+    }
+
+    public float getScaleFactor() {
+        return mScaleFactor;
+    }
+
+    public void setQuality(int quality) {
+        mQuality = quality;
+    }
+
+    public int getQuality() {
+        return mQuality;
+    }
+
+    public void setFiltersManager(FiltersManager filtersManager) {
+        mFiltersManager = filtersManager;
+    }
+
+    public FiltersManager getFiltersManager() {
+        return mFiltersManager;
+    }
+
+    public void applyRepresentation(FilterRepresentation representation,
+                                    Allocation in, Allocation out) {
+        ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation);
+        filter.useRepresentation(representation);
+        filter.setEnvironment(this);
+        if (filter.supportsAllocationInput()) {
+            filter.apply(in, out);
+        }
+        filter.setEnvironment(null);
+    }
+
+    public Bitmap applyRepresentation(FilterRepresentation representation, Bitmap bitmap) {
+        ImageFilter filter = mFiltersManager.getFilterForRepresentation(representation);
+        filter.useRepresentation(representation);
+        filter.setEnvironment(this);
+        Bitmap ret = filter.apply(bitmap, mScaleFactor, mQuality);
+        filter.setEnvironment(null);
+        return ret;
+    }
+
+    public CachingPipeline getCachingPipeline() {
+        return mCachingPipeline;
+    }
+
+    public void setCachingPipeline(CachingPipeline cachingPipeline) {
+        mCachingPipeline = cachingPipeline;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index 2522c89..2a7e601 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -17,13 +17,20 @@
 package com.android.gallery3d.filtershow.presets;
 
 import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.support.v8.renderscript.Allocation;
 import android.util.Log;
 
-import com.android.gallery3d.filtershow.ImageStateAdapter;
+import com.android.gallery3d.filtershow.cache.CachingPipeline;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
-import com.android.gallery3d.filtershow.imageshow.ImageShow;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.state.State;
+import com.android.gallery3d.filtershow.state.StateAdapter;
+import com.android.gallery3d.util.UsageStatistics;
 
 import java.util.Vector;
 
@@ -31,13 +38,16 @@
 
     private static final String LOGTAG = "ImagePreset";
 
-    private ImageShow mEndPoint = null;
-    private ImageFilter mImageBorder = null;
-    private float mScaleFactor = 1.0f;
-    private boolean mIsHighQuality = false;
+    private FilterRepresentation mBorder = null;
+    public static final int QUALITY_ICON = 0;
+    public static final int QUALITY_PREVIEW = 1;
+    public static final int QUALITY_FINAL = 2;
+    public static final int STYLE_ICON = 3;
+
     private ImageLoader mImageLoader = null;
 
-    protected Vector<ImageFilter> mFilters = new Vector<ImageFilter>();
+    private Vector<FilterRepresentation> mFilters = new Vector<FilterRepresentation>();
+
     protected String mName = "Original";
     private String mHistoryName = "Original";
     protected boolean mIsFxPreset = false;
@@ -46,10 +56,10 @@
     private boolean mDoApplyFilters = true;
 
     public final GeometryMetadata mGeoData = new GeometryMetadata();
+    private boolean mPartialRendering = false;
+    private Rect mPartialRenderingBounds;
 
-    enum FullRotate {
-        ZERO, NINETY, HUNDRED_EIGHTY, TWO_HUNDRED_SEVENTY
-    }
+    private Bitmap mPreviewImage;
 
     public ImagePreset() {
         setup();
@@ -69,13 +79,12 @@
 
     public ImagePreset(ImagePreset source) {
         try {
-            if (source.mImageBorder != null) {
-                mImageBorder = source.mImageBorder.clone();
+            if (source.mBorder != null) {
+                mBorder = source.mBorder.clone();
             }
             for (int i = 0; i < source.mFilters.size(); i++) {
-                ImageFilter filter = source.mFilters.elementAt(i).clone();
-                filter.setImagePreset(this);
-                add(filter);
+                FilterRepresentation representation = source.mFilters.elementAt(i).clone();
+                addFilter(representation);
             }
         } catch (java.lang.CloneNotSupportedException e) {
             Log.v(LOGTAG, "Exception trying to clone: " + e);
@@ -84,10 +93,82 @@
         mHistoryName = source.name();
         mIsFxPreset = source.isFx();
         mImageLoader = source.getImageLoader();
+        mPreviewImage = source.getPreviewImage();
 
         mGeoData.set(source.mGeoData);
     }
 
+    public FilterRepresentation getFilterRepresentation(int position) {
+        FilterRepresentation representation = null;
+        try {
+            representation = mFilters.elementAt(position).clone();
+        } catch (CloneNotSupportedException e) {
+            e.printStackTrace();
+        }
+        return representation;
+    }
+
+    public int getPositionForRepresentation(FilterRepresentation representation) {
+        for (int i = 0; i < mFilters.size(); i++) {
+            if (mFilters.elementAt(i).getFilterClass() == representation.getFilterClass()) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public FilterRepresentation getFilterRepresentationCopyFrom(FilterRepresentation filterRepresentation) {
+        // TODO: add concept of position in the filters (to allow multiple instances)
+        if (filterRepresentation == null) {
+            return null;
+        }
+        FilterRepresentation representation = null;
+        if ((mBorder != null)
+                && (mBorder.getFilterClass() == filterRepresentation.getFilterClass())) {
+            // TODO: instead of special casing for border, we should correctly implements "filters priority set"
+            representation = mBorder;
+        } else {
+            int position = getPositionForRepresentation(filterRepresentation);
+            if (position == -1) {
+                return null;
+            }
+            representation = mFilters.elementAt(position);
+        }
+        if (representation != null) {
+            try {
+                representation = representation.clone();
+            } catch (CloneNotSupportedException e) {
+                e.printStackTrace();
+            }
+        }
+        return representation;
+    }
+
+    public void updateFilterRepresentation(FilterRepresentation representation) {
+        if (representation == null) {
+            return;
+        }
+        synchronized (mFilters) {
+            if (representation instanceof GeometryMetadata) {
+                setGeometry((GeometryMetadata) representation);
+            } else {
+                if ((mBorder != null)
+                        && (mBorder.getFilterClass() == representation.getFilterClass())) {
+                    mBorder.updateTempParametersFrom(representation);
+                } else {
+                    int position = getPositionForRepresentation(representation);
+                    if (position == -1) {
+                        return;
+                    }
+                    FilterRepresentation old = mFilters.elementAt(position);
+                    old.updateTempParametersFrom(representation);
+                }
+            }
+        }
+        MasterImage.getImage().invalidatePreview();
+        fillImageStateAdapter(MasterImage.getImage().getState());
+    }
+
     public void setDoApplyGeometry(boolean value) {
         mDoApplyGeometry = value;
     }
@@ -96,16 +177,24 @@
         mDoApplyFilters = value;
     }
 
+    public boolean getDoApplyFilters() {
+        return mDoApplyFilters;
+    }
+
+    public synchronized GeometryMetadata getGeometry() {
+        return mGeoData;
+    }
+
     public boolean hasModifications() {
-        if (mImageBorder != null && !mImageBorder.isNil()) {
+        if (mBorder != null && !mBorder.isNil()) {
             return true;
         }
         if (mGeoData.hasModifications()) {
             return true;
         }
         for (int i = 0; i < mFilters.size(); i++) {
-            ImageFilter filter = mFilters.elementAt(i);
-            if (!filter.isNil()) {
+            FilterRepresentation filter = mFilters.elementAt(i);
+            if (!filter.isNil() && !filter.getName().equalsIgnoreCase("none")) {
                 return true;
             }
         }
@@ -113,31 +202,36 @@
     }
 
     public boolean isPanoramaSafe() {
-        if (mImageBorder != null && !mImageBorder.isNil()) {
+        if (mBorder != null && !mBorder.isNil()) {
             return false;
         }
         if (mGeoData.hasModifications()) {
             return false;
         }
-        for (ImageFilter filter : mFilters) {
-            if (filter.getFilterType() == ImageFilter.TYPE_VIGNETTE
-                    && !filter.isNil()) {
+        for (FilterRepresentation representation : mFilters) {
+            if (representation.getPriority() == FilterRepresentation.TYPE_VIGNETTE
+                && !representation.isNil()) {
                 return false;
             }
-            if (filter.getFilterType() == ImageFilter.TYPE_TINYPLANET
-                    && !filter.isNil()) {
+            if (representation.getPriority() == FilterRepresentation.TYPE_TINYPLANET
+                && !representation.isNil()) {
                 return false;
             }
         }
         return true;
     }
 
-    public void setGeometry(GeometryMetadata m) {
+    public synchronized void setGeometry(GeometryMetadata m) {
         mGeoData.set(m);
+        MasterImage.getImage().notifyGeometryChange();
     }
 
-    private void setBorder(ImageFilter filter) {
-        mImageBorder = filter;
+    private void setBorder(FilterRepresentation filter) {
+        mBorder = filter;
+    }
+
+    public void resetBorder() {
+        mBorder = null;
     }
 
     public boolean isFx() {
@@ -165,10 +259,31 @@
         this.mImageLoader = mImageLoader;
     }
 
+    public boolean equals(ImagePreset preset) {
+        if (!same(preset)) {
+            return false;
+        }
+        if (mDoApplyFilters && preset.mDoApplyFilters) {
+            for (int i = 0; i < preset.mFilters.size(); i++) {
+                FilterRepresentation a = preset.mFilters.elementAt(i);
+                FilterRepresentation b = mFilters.elementAt(i);
+                if (!a.equals(b)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
     public boolean same(ImagePreset preset) {
+        if (preset == null) {
+            return false;
+        }
+
         if (preset.mFilters.size() != mFilters.size()) {
             return false;
         }
+
         if (!mName.equalsIgnoreCase(preset.name())) {
             return false;
         }
@@ -181,11 +296,11 @@
             return false;
         }
 
-        if (mDoApplyGeometry && mImageBorder != preset.mImageBorder) {
+        if (mDoApplyGeometry && mBorder != preset.mBorder) {
             return false;
         }
 
-        if (mImageBorder != null && !mImageBorder.same(preset.mImageBorder)) {
+        if (mBorder != null && !mBorder.equals(preset.mBorder)) {
             return false;
         }
 
@@ -197,16 +312,39 @@
 
         if (mDoApplyFilters && preset.mDoApplyFilters) {
             for (int i = 0; i < preset.mFilters.size(); i++) {
-                ImageFilter a = preset.mFilters.elementAt(i);
-                ImageFilter b = mFilters.elementAt(i);
+                FilterRepresentation a = preset.mFilters.elementAt(i);
+                FilterRepresentation b = mFilters.elementAt(i);
                 if (!a.same(b)) {
                     return false;
                 }
             }
         }
+
         return true;
     }
 
+    public int similarUpTo(ImagePreset preset) {
+        if (!mGeoData.equals(preset.mGeoData)) {
+            return -1;
+        }
+
+        for (int i = 0; i < preset.mFilters.size(); i++) {
+            FilterRepresentation a = preset.mFilters.elementAt(i);
+            if (i < mFilters.size()) {
+                FilterRepresentation b = mFilters.elementAt(i);
+                if (!a.same(b)) {
+                    return i;
+                }
+                if (!a.equals(b)) {
+                    return i;
+                }
+            } else {
+                return i;
+            }
+        }
+        return preset.mFilters.size();
+    }
+
     public String name() {
         return mName;
     }
@@ -215,57 +353,86 @@
         return mHistoryName;
     }
 
-    public void add(ImageFilter filter) {
+    public void showFilters() {
+        Log.v(LOGTAG, "\\\\\\ showFilters -- " + mFilters.size() + " filters");
+        int n = 0;
+        for (FilterRepresentation representation : mFilters) {
+            Log.v(LOGTAG, " filter " + n + " : " + representation.toString());
+            n++;
+        }
+        Log.v(LOGTAG, "/// showFilters -- " + mFilters.size() + " filters");
+    }
 
-        if (filter.getFilterType() == ImageFilter.TYPE_BORDER) {
-            setHistoryName(filter.getName());
-            setBorder(filter);
-        } else if (filter.getFilterType() == ImageFilter.TYPE_FX) {
+    public FilterRepresentation getLastRepresentation() {
+        if (mFilters.size() > 0) {
+            return mFilters.lastElement();
+        }
+        return null;
+    }
+
+    public void removeFilter(FilterRepresentation filterRepresentation) {
+        if (filterRepresentation.getPriority() == FilterRepresentation.TYPE_BORDER) {
+            setBorder(null);
+            setHistoryName("Remove");
+            return;
+        }
+        for (int i = 0; i < mFilters.size(); i++) {
+            if (mFilters.elementAt(i).getFilterClass() == filterRepresentation.getFilterClass()) {
+                mFilters.remove(i);
+                setHistoryName("Remove");
+                return;
+            }
+        }
+    }
+
+    public void addFilter(FilterRepresentation representation) {
+        if (representation instanceof GeometryMetadata) {
+            setGeometry((GeometryMetadata) representation);
+            return;
+        }
+        if (representation.getPriority() == FilterRepresentation.TYPE_BORDER) {
+            setHistoryName(representation.getName());
+            setBorder(representation);
+        } else if (representation.getPriority() == FilterRepresentation.TYPE_FX) {
             boolean found = false;
             for (int i = 0; i < mFilters.size(); i++) {
-                byte type = mFilters.get(i).getFilterType();
+                int type = mFilters.elementAt(i).getPriority();
                 if (found) {
-                    if (type != ImageFilter.TYPE_VIGNETTE) {
+                    if (type != FilterRepresentation.TYPE_VIGNETTE) {
                         mFilters.remove(i);
                         continue;
                     }
                 }
-                if (type == ImageFilter.TYPE_FX) {
+                if (type == FilterRepresentation.TYPE_FX) {
                     mFilters.remove(i);
-                    mFilters.add(i, filter);
-                    setHistoryName(filter.getName());
+                    mFilters.add(i, representation);
+                    setHistoryName(representation.getName());
                     found = true;
                 }
             }
             if (!found) {
-                mFilters.add(filter);
-                setHistoryName(filter.getName());
+                mFilters.add(representation);
+                setHistoryName(representation.getName());
             }
         } else {
-            mFilters.add(filter);
-            setHistoryName(filter.getName());
-        }
-        filter.setImagePreset(this);
-    }
-
-    public void remove(String filterName) {
-        ImageFilter filter = getFilter(filterName);
-        if (filter != null) {
-            mFilters.remove(filter);
+            mFilters.add(representation);
+            setHistoryName(representation.getName());
         }
     }
 
-    public int getCount() {
-        return mFilters.size();
-    }
-
-    public ImageFilter getFilter(String name) {
+    public FilterRepresentation getRepresentation(FilterRepresentation filterRepresentation) {
+        if (filterRepresentation instanceof GeometryMetadata) {
+            return mGeoData;
+        }
         for (int i = 0; i < mFilters.size(); i++) {
-            ImageFilter filter = mFilters.elementAt(i);
-            if (filter.getName().equalsIgnoreCase(name)) {
-                return filter;
+            FilterRepresentation representation = mFilters.elementAt(i);
+            if (representation.getFilterClass() == filterRepresentation.getFilterClass()) {
+                return representation;
             }
         }
+        if (mBorder != null && mBorder.getFilterClass() == filterRepresentation.getFilterClass()) {
+            return mBorder;
+        }
         return null;
     }
 
@@ -273,58 +440,169 @@
         // do nothing here
     }
 
-    public void setEndpoint(ImageShow image) {
-        mEndPoint = image;
+    public Bitmap apply(Bitmap original, FilterEnvironment environment) {
+        Bitmap bitmap = original;
+        bitmap = applyFilters(bitmap, -1, -1, environment);
+        return applyBorder(bitmap, environment);
     }
 
-    public Bitmap apply(Bitmap original) {
-        // First we apply any transform -- 90 rotate, flip, straighten, crop
-        Bitmap bitmap = original;
-
+    public Bitmap applyGeometry(Bitmap bitmap, FilterEnvironment environment) {
+        // Apply any transform -- 90 rotate, flip, straighten, crop
+        // Returns a new bitmap.
         if (mDoApplyGeometry) {
-            bitmap = mGeoData.apply(original, mScaleFactor, mIsHighQuality);
+            mGeoData.synchronizeRepresentation();
+            bitmap = environment.applyRepresentation(mGeoData, bitmap);
         }
+        return bitmap;
+    }
 
-        if (mDoApplyFilters) {
-            for (int i = 0; i < mFilters.size(); i++) {
-                ImageFilter filter = mFilters.elementAt(i);
-                bitmap = filter.apply(bitmap, mScaleFactor, mIsHighQuality);
+    public Bitmap applyBorder(Bitmap bitmap, FilterEnvironment environment) {
+        if (mBorder != null && mDoApplyGeometry) {
+            mBorder.synchronizeRepresentation();
+            bitmap = environment.applyRepresentation(mBorder, bitmap);
+            if (environment.getQuality() == QUALITY_FINAL) {
+                UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+                        "SaveBorder", mBorder.getName(), 1);
             }
         }
+        return bitmap;
+    }
 
-        if (mImageBorder != null && mDoApplyGeometry) {
-            bitmap = mImageBorder.apply(bitmap, mScaleFactor, mIsHighQuality);
-        }
-
-        if (mEndPoint != null) {
-            mEndPoint.updateFilteredImage(bitmap);
+    public Bitmap applyFilters(Bitmap bitmap, int from, int to, FilterEnvironment environment) {
+        if (mDoApplyFilters) {
+            if (from < 0) {
+                from = 0;
+            }
+            if (to == -1) {
+                to = mFilters.size();
+            }
+            for (int i = from; i < to; i++) {
+                FilterRepresentation representation = null;
+                synchronized (mFilters) {
+                    representation = mFilters.elementAt(i);
+                    representation.synchronizeRepresentation();
+                }
+                bitmap = environment.applyRepresentation(representation, bitmap);
+                if (environment.getQuality() == QUALITY_FINAL) {
+                    UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
+                            "SaveFilter", representation.getName(), 1);
+                }
+                if (environment.needsStop()) {
+                    return bitmap;
+                }
+            }
         }
 
         return bitmap;
     }
 
-    public void fillImageStateAdapter(ImageStateAdapter imageStateAdapter) {
+    public void applyBorder(Allocation in, Allocation out, FilterEnvironment environment) {
+        if (mBorder != null && mDoApplyGeometry) {
+            mBorder.synchronizeRepresentation();
+            // TODO: should keep the bitmap around
+            Allocation bitmapIn = Allocation.createTyped(CachingPipeline.getRenderScriptContext(), in.getType());
+            bitmapIn.copyFrom(out);
+            environment.applyRepresentation(mBorder, bitmapIn, out);
+        }
+    }
+
+    public void applyFilters(int from, int to, Allocation in, Allocation out, FilterEnvironment environment) {
+        if (mDoApplyFilters) {
+            if (from < 0) {
+                from = 0;
+            }
+            if (to == -1) {
+                to = mFilters.size();
+            }
+            for (int i = from; i < to; i++) {
+                FilterRepresentation representation = null;
+                synchronized (mFilters) {
+                    representation = mFilters.elementAt(i);
+                    representation.synchronizeRepresentation();
+                }
+                if (i > from) {
+                    in.copyFrom(out);
+                }
+                environment.applyRepresentation(representation, in, out);
+            }
+        }
+    }
+
+    public boolean canDoPartialRendering() {
+        if (mGeoData.hasModifications()) {
+            return false;
+        }
+        if (mBorder != null && !mBorder.supportsPartialRendering()) {
+            return false;
+        }
+        if (ImageLoader.getZoomOrientation() != ImageLoader.ORI_NORMAL) {
+            return false;
+        }
+        for (int i = 0; i < mFilters.size(); i++) {
+            FilterRepresentation representation = null;
+            synchronized (mFilters) {
+                representation = mFilters.elementAt(i);
+            }
+            if (!representation.supportsPartialRendering()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void fillImageStateAdapter(StateAdapter imageStateAdapter) {
         if (imageStateAdapter == null) {
             return;
         }
-        imageStateAdapter.clear();
-        imageStateAdapter.addAll(mFilters);
-        imageStateAdapter.notifyDataSetChanged();
+        Vector<State> states = new Vector<State>();
+        // TODO: supports Geometry representations in the state panel.
+        if (false && mGeoData != null && mGeoData.hasModifications()) {
+            State geo = new State("Geometry");
+            geo.setFilterRepresentation(mGeoData);
+            states.add(geo);
+        }
+        for (FilterRepresentation filter : mFilters) {
+            State state = new State(filter.getName());
+            state.setFilterRepresentation(filter);
+            states.add(state);
+        }
+        if (mBorder != null) {
+            State border = new State(mBorder.getName());
+            border.setFilterRepresentation(mBorder);
+            states.add(border);
+        }
+        imageStateAdapter.fill(states);
     }
 
-    public float getScaleFactor() {
-        return mScaleFactor;
+    public void setPartialRendering(boolean partialRendering, Rect bounds) {
+        mPartialRendering = partialRendering;
+        mPartialRenderingBounds = bounds;
     }
 
-    public boolean isHighQuality() {
-        return mIsHighQuality;
+    public boolean isPartialRendering() {
+        return mPartialRendering;
     }
 
-    public void setIsHighQuality(boolean value) {
-        mIsHighQuality = value;
+    public Rect getPartialRenderingBounds() {
+        return mPartialRenderingBounds;
     }
 
-    public void setScaleFactor(float value) {
-        mScaleFactor = value;
+    public Bitmap getPreviewImage() {
+        return mPreviewImage;
     }
+
+    public void setPreviewImage(Bitmap previewImage) {
+        mPreviewImage = previewImage;
+    }
+
+    public Vector<ImageFilter> getUsedFilters(BaseFiltersManager filtersManager) {
+        Vector<ImageFilter> usedFilters = new Vector<ImageFilter>();
+        for (int i = 0; i < mFilters.size(); i++) {
+            FilterRepresentation representation = mFilters.elementAt(i);
+            ImageFilter filter = filtersManager.getFilterForRepresentation(representation);
+            usedFilters.add(filter);
+        }
+        return usedFilters;
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java
deleted file mode 100644
index bfa6dac..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterBW;
-
-public class ImagePresetBW extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "B&W";
-    }
-
-    @Override
-    public void setup() {
-        mFilters.add(new ImageFilterBW());
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java
deleted file mode 100644
index 5d56aa1..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterBWBlue;
-
-public class ImagePresetBWBlue extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "B&W - Blue";
-    }
-
-    @Override
-    public void setup() {
-        mFilters.add(new ImageFilterBWBlue());
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java
deleted file mode 100644
index d1b4e5d..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterBWGreen;
-
-public class ImagePresetBWGreen extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "B&W - Green";
-    }
-
-    @Override
-    public void setup() {
-        mFilters.add(new ImageFilterBWGreen());
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java
deleted file mode 100644
index 9653bed..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterBWRed;
-
-public class ImagePresetBWRed extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "B&W - Red";
-    }
-
-    @Override
-    public void setup() {
-        mFilters.add(new ImageFilterBWRed());
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java
deleted file mode 100644
index 95edc5d..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterFx;
-
-public class ImagePresetFX extends ImagePreset {
-    String name;
-    Bitmap fxBitmap;
-
-    @Override
-    public String name() {
-        return name;
-    }
-
-    public ImagePresetFX(Bitmap bitmap, String name) {
-        fxBitmap = bitmap;
-        this.name = name;
-        setup();
-    }
-
-    @Override
-    public void setup() {
-        if (fxBitmap != null) {
-            mFilters.add(new ImageFilterFx(fxBitmap,name));
-        }
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java
deleted file mode 100644
index 5e1db83..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import android.graphics.Color;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
-
-public class ImagePresetOld extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "Old";
-    }
-
-    @Override
-    public void setup() {
-        ImageFilterGradient filter = new ImageFilterGradient();
-        filter.addColor(Color.BLACK, 0.0f);
-        filter.addColor(Color.argb(255, 228, 231, 193), 1.0f);
-        mFilters.add(filter);
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java
deleted file mode 100644
index ddfca75..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterSaturated;
-
-public class ImagePresetSaturated extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "Saturated";
-    }
-
-    @Override
-    public void setup() {
-        ImageFilterSaturated filter = new ImageFilterSaturated();
-        filter.setParameter(50);
-        mFilters.add(filter);
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java
deleted file mode 100644
index 7957b5e..0000000
--- a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.presets;
-
-import android.graphics.Color;
-
-import com.android.gallery3d.filtershow.filters.ImageFilterGradient;
-
-public class ImagePresetXProcessing extends ImagePreset {
-
-    @Override
-    public String name() {
-        return "X-Process";
-    }
-
-    @Override
-    public void setup() {
-        ImageFilterGradient filter = new ImageFilterGradient();
-        filter.addColor(Color.BLACK, 0.0f);
-        filter.addColor(Color.argb(255, 29, 82, 83), 0.4f);
-        filter.addColor(Color.argb(255, 211, 217, 186), 1.0f);
-        mFilters.add(filter);
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/state/DragListener.java b/src/com/android/gallery3d/filtershow/state/DragListener.java
new file mode 100644
index 0000000..1aa81ed
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/DragListener.java
@@ -0,0 +1,110 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+
+class DragListener implements View.OnDragListener {
+
+    private static final String LOGTAG = "DragListener";
+    private PanelTrack mStatePanelTrack;
+    private static float sSlope = 0.2f;
+
+    public DragListener(PanelTrack statePanelTrack) {
+        mStatePanelTrack = statePanelTrack;
+    }
+
+    private void setState(DragEvent event) {
+        float translation = event.getY() - mStatePanelTrack.getTouchPoint().y;
+        float alpha = 1.0f - (Math.abs(translation)
+                / mStatePanelTrack.getCurrentView().getHeight());
+        if (mStatePanelTrack.getOrientation() == LinearLayout.VERTICAL) {
+            translation = event.getX() - mStatePanelTrack.getTouchPoint().x;
+            alpha = 1.0f - (Math.abs(translation)
+                    / mStatePanelTrack.getCurrentView().getWidth());
+            mStatePanelTrack.getCurrentView().setTranslationX(translation);
+        } else {
+            mStatePanelTrack.getCurrentView().setTranslationY(translation);
+        }
+        mStatePanelTrack.getCurrentView().setBackgroundAlpha(alpha);
+    }
+
+    @Override
+    public boolean onDrag(View v, DragEvent event) {
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED: {
+                break;
+            }
+            case DragEvent.ACTION_DRAG_LOCATION: {
+                if (mStatePanelTrack.getCurrentView() != null) {
+                    setState(event);
+                    View over = mStatePanelTrack.findChildAt((int) event.getX(),
+                                                             (int) event.getY());
+                    if (over != null && over != mStatePanelTrack.getCurrentView()) {
+                        StateView stateView = (StateView) over;
+                        if (stateView != mStatePanelTrack.getCurrentView()) {
+                            int pos = mStatePanelTrack.findChild(over);
+                            int origin = mStatePanelTrack.findChild(
+                                    mStatePanelTrack.getCurrentView());
+                            ArrayAdapter array = (ArrayAdapter) mStatePanelTrack.getAdapter();
+                            if (origin != -1 && pos != -1) {
+                                State current = (State) array.getItem(origin);
+                                array.remove(current);
+                                array.insert(current, pos);
+                                mStatePanelTrack.fillContent(false);
+                                mStatePanelTrack.setCurrentView(mStatePanelTrack.getChildAt(pos));
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+            case DragEvent.ACTION_DRAG_ENTERED: {
+                mStatePanelTrack.setExited(false);
+                if (mStatePanelTrack.getCurrentView() != null) {
+                    mStatePanelTrack.getCurrentView().setVisibility(View.VISIBLE);
+                }
+                return true;
+            }
+            case DragEvent.ACTION_DRAG_EXITED: {
+                if (mStatePanelTrack.getCurrentView() != null) {
+                    setState(event);
+                    mStatePanelTrack.getCurrentView().setVisibility(View.INVISIBLE);
+                }
+                mStatePanelTrack.setExited(true);
+                break;
+            }
+            case DragEvent.ACTION_DROP: {
+                break;
+            }
+            case DragEvent.ACTION_DRAG_ENDED: {
+                if (mStatePanelTrack.getCurrentView() != null
+                        && mStatePanelTrack.getCurrentView().getAlpha() > sSlope) {
+                    setState(event);
+                }
+                mStatePanelTrack.checkEndState();
+                break;
+            }
+            default:
+                break;
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/PanelTrack.java b/src/com/android/gallery3d/filtershow/state/PanelTrack.java
new file mode 100644
index 0000000..d02207d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/PanelTrack.java
@@ -0,0 +1,37 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.graphics.Point;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Adapter;
+
+public interface PanelTrack {
+    public int getOrientation();
+    public void onTouch(MotionEvent event, StateView view);
+    public StateView getCurrentView();
+    public void setCurrentView(View view);
+    public Point getTouchPoint();
+    public View findChildAt(int x, int y);
+    public int findChild(View view);
+    public Adapter getAdapter();
+    public void fillContent(boolean value);
+    public View getChildAt(int pos);
+    public void setExited(boolean value);
+    public void checkEndState();
+}
diff --git a/src/com/android/gallery3d/filtershow/state/State.java b/src/com/android/gallery3d/filtershow/state/State.java
new file mode 100644
index 0000000..29bbf91
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/State.java
@@ -0,0 +1,74 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
+public class State {
+    private String mText;
+    private int mType;
+    private FilterRepresentation mFilterRepresentation;
+
+    public State(State state) {
+        this(state.getText(), state.getType());
+    }
+
+    public State(String text) {
+       this(text, StateView.DEFAULT);
+    }
+
+    public State(String text, int type) {
+        mText = text;
+        mType = type;
+    }
+
+    public boolean equals(State state) {
+        if (mFilterRepresentation.getFilterClass()
+                != state.mFilterRepresentation.getFilterClass()) {
+            return false;
+        }
+        return true;
+    }
+
+    public boolean isDraggable() {
+        return mFilterRepresentation != null;
+    }
+
+    String getText() {
+        return mText;
+    }
+
+    void setText(String text) {
+        mText = text;
+    }
+
+    int getType() {
+        return mType;
+    }
+
+    void setType(int type) {
+        mType = type;
+    }
+
+    public FilterRepresentation getFilterRepresentation() {
+        return mFilterRepresentation;
+    }
+
+    public void setFilterRepresentation(FilterRepresentation filterRepresentation) {
+        mFilterRepresentation = filterRepresentation;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StateAdapter.java b/src/com/android/gallery3d/filtershow/state/StateAdapter.java
new file mode 100644
index 0000000..5225852
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StateAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+import java.util.Vector;
+
+public class StateAdapter extends ArrayAdapter<State> {
+
+    private static final String LOGTAG = "StateAdapter";
+    private int mOrientation;
+    private String mOriginalText;
+    private String mResultText;
+
+    public StateAdapter(Context context, int textViewResourceId) {
+        super(context, textViewResourceId);
+        mOriginalText = context.getString(R.string.state_panel_original);
+        mResultText = context.getString(R.string.state_panel_result);
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        StateView view = null;
+        if (convertView == null) {
+            convertView = new StateView(getContext());
+        }
+        view = (StateView) convertView;
+        State state = getItem(position);
+        view.setState(state);
+        view.setOrientation(mOrientation);
+        FilterRepresentation currentRep = MasterImage.getImage().getCurrentFilterRepresentation();
+        FilterRepresentation stateRep = state.getFilterRepresentation();
+        if (currentRep != null && stateRep != null
+            && currentRep.getFilterClass() == stateRep.getFilterClass()
+            && currentRep.getEditorId() != ImageOnlyEditor.ID) {
+            view.setSelected(true);
+        } else {
+            view.setSelected(false);
+        }
+        return view;
+    }
+
+    public boolean contains(State state) {
+        for (int i = 0; i < getCount(); i++) {
+            if (state == getItem(i)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    public void addOriginal() {
+        add(new State(mOriginalText));
+    }
+
+    public boolean same(Vector<State> states) {
+        // we have the original state in addition
+        if (states.size() + 1 != getCount()) {
+            return false;
+        }
+        for (int i = 1; i < getCount(); i++) {
+            State state = getItem(i);
+            if (!state.equals(states.elementAt(i-1))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void fill(Vector<State> states) {
+        if (same(states)) {
+            return;
+        }
+        clear();
+        addOriginal();
+        addAll(states);
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public void remove(State state) {
+        super.remove(state);
+        FilterRepresentation filterRepresentation = state.getFilterRepresentation();
+        FilterShowActivity activity = (FilterShowActivity) getContext();
+        activity.removeFilterRepresentation(filterRepresentation);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StatePanel.java b/src/com/android/gallery3d/filtershow/state/StatePanel.java
new file mode 100644
index 0000000..df470f2
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StatePanel.java
@@ -0,0 +1,44 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StatePanel extends Fragment {
+    private static final String LOGTAG = "StatePanel";
+    private StatePanelTrack track;
+    private LinearLayout mMainView;
+    public static final String FRAGMENT_TAG = "StatePanel";
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        mMainView = (LinearLayout) inflater.inflate(R.layout.filtershow_state_panel_new, null);
+        View panel = mMainView.findViewById(R.id.listStates);
+        track = (StatePanelTrack) panel;
+        track.setAdapter(MasterImage.getImage().getState());
+        return mMainView;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java b/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java
new file mode 100644
index 0000000..fff7e7f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StatePanelTrack.java
@@ -0,0 +1,351 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.animation.LayoutTransition;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StatePanelTrack extends LinearLayout implements PanelTrack {
+
+    private static final String LOGTAG = "StatePanelTrack";
+    private Point mTouchPoint;
+    private StateView mCurrentView;
+    private StateView mCurrentSelectedView;
+    private boolean mExited = false;
+    private boolean mStartedDrag = false;
+    private StateAdapter mAdapter;
+    private DragListener mDragListener = new DragListener(this);
+    private float mDeleteSlope = 0.2f;
+    private GestureDetector mGestureDetector;
+    private int mElemWidth;
+    private int mElemHeight;
+    private int mElemSize;
+    private int mElemEndSize;
+    private int mEndElemWidth;
+    private int mEndElemHeight;
+    private long mTouchTime;
+    private int mMaxTouchDelay = 300; // 300ms delay for touch
+    private static final boolean ALLOWS_DRAG = false;
+    private DataSetObserver mObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            super.onChanged();
+            fillContent(false);
+        }
+
+        @Override
+        public void onInvalidated() {
+            super.onInvalidated();
+            fillContent(false);
+        }
+    };
+
+    public StatePanelTrack(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StatePanelTrack);
+        mElemSize = a.getDimensionPixelSize(R.styleable.StatePanelTrack_elemSize, 0);
+        mElemEndSize = a.getDimensionPixelSize(R.styleable.StatePanelTrack_elemEndSize, 0);
+        if (getOrientation() == LinearLayout.HORIZONTAL) {
+            mElemWidth = mElemSize;
+            mElemHeight = LayoutParams.MATCH_PARENT;
+            mEndElemWidth = mElemEndSize;
+            mEndElemHeight = LayoutParams.MATCH_PARENT;
+        } else {
+            mElemWidth = LayoutParams.MATCH_PARENT;
+            mElemHeight = mElemSize;
+            mEndElemWidth = LayoutParams.MATCH_PARENT;
+            mEndElemHeight = mElemEndSize;
+        }
+        GestureDetector.SimpleOnGestureListener simpleOnGestureListener
+                = new GestureDetector.SimpleOnGestureListener(){
+            @Override
+            public void onLongPress(MotionEvent e) {
+                longPress(e);
+            }
+            @Override
+            public boolean onDoubleTap(MotionEvent e) {
+                addDuplicate(e);
+                return true;
+            }
+        };
+        mGestureDetector = new GestureDetector(context, simpleOnGestureListener);
+    }
+
+    private void addDuplicate(MotionEvent e) {
+        if (mCurrentSelectedView == null) {
+            return;
+        }
+        int pos = findChild(mCurrentSelectedView);
+        if (pos != -1) {
+            mAdapter.insert(new State(mCurrentSelectedView.getState()), pos);
+            fillContent(true);
+        }
+    }
+
+    private void longPress(MotionEvent e) {
+        View view = findChildAt((int) e.getX(), (int) e.getY());
+        if (view == null) {
+            return;
+        }
+        if (view instanceof StateView) {
+            StateView stateView = (StateView) view;
+            stateView.setDuplicateButton(true);
+        }
+    }
+
+    public void setAdapter(StateAdapter adapter) {
+        mAdapter = adapter;
+        mAdapter.registerDataSetObserver(mObserver);
+        mAdapter.setOrientation(getOrientation());
+        fillContent(false);
+        requestLayout();
+    }
+
+    public StateView findChildWithState(State state) {
+        for (int i = 0; i < getChildCount(); i++) {
+            StateView view = (StateView) getChildAt(i);
+            if (view.getState() == state) {
+                return view;
+            }
+        }
+        return null;
+    }
+
+    public void fillContent(boolean animate) {
+        if (!animate) {
+            this.setLayoutTransition(null);
+        }
+        int n = mAdapter.getCount();
+        for (int i = 0; i < getChildCount(); i++) {
+            StateView child = (StateView) getChildAt(i);
+            child.resetPosition();
+            if (!mAdapter.contains(child.getState())) {
+                removeView(child);
+            }
+        }
+        LayoutParams params = new LayoutParams(mElemWidth, mElemHeight);
+        for (int i = 0; i < n; i++) {
+            State s = mAdapter.getItem(i);
+            if (findChildWithState(s) == null) {
+                View view = mAdapter.getView(i, null, this);
+                addView(view, i, params);
+            }
+        }
+
+        for (int i = 0; i < n; i++) {
+            State state = mAdapter.getItem(i);
+            StateView view = (StateView) getChildAt(i);
+            view.setState(state);
+            if (i == 0) {
+                view.setType(StateView.BEGIN);
+            } else if (i == n - 1) {
+                view.setType(StateView.END);
+            } else {
+                view.setType(StateView.DEFAULT);
+            }
+            view.resetPosition();
+        }
+
+        if (!animate) {
+            this.setLayoutTransition(new LayoutTransition());
+        }
+    }
+
+    public void onTouch(MotionEvent event, StateView view) {
+        if (!view.isDraggable()) {
+            return;
+        }
+        mCurrentView = view;
+        if (mCurrentSelectedView == mCurrentView) {
+            return;
+        }
+        if (mCurrentSelectedView != null) {
+            mCurrentSelectedView.setSelected(false);
+        }
+        // We changed the current view -- let's reset the
+        // gesture detector.
+        MotionEvent cancelEvent = MotionEvent.obtain(event);
+        cancelEvent.setAction(MotionEvent.ACTION_CANCEL);
+        mGestureDetector.onTouchEvent(cancelEvent);
+        mCurrentSelectedView = mCurrentView;
+        // We have to send the event to the gesture detector
+        mGestureDetector.onTouchEvent(event);
+        mTouchTime = System.currentTimeMillis();
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (mCurrentView != null) {
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mCurrentView == null) {
+            return false;
+        }
+        if (mTouchTime == 0) {
+            mTouchTime = System.currentTimeMillis();
+        }
+        mGestureDetector.onTouchEvent(event);
+        if (mTouchPoint == null) {
+            mTouchPoint = new Point();
+            mTouchPoint.x = (int) event.getX();
+            mTouchPoint.y = (int) event.getY();
+        }
+
+        if (event.getActionMasked() == MotionEvent.ACTION_MOVE) {
+            float translation = event.getY() - mTouchPoint.y;
+            float alpha = 1.0f - (Math.abs(translation) / mCurrentView.getHeight());
+            if (getOrientation() == LinearLayout.VERTICAL) {
+                translation = event.getX() - mTouchPoint.x;
+                alpha = 1.0f - (Math.abs(translation) / mCurrentView.getWidth());
+                mCurrentView.setTranslationX(translation);
+            } else {
+                mCurrentView.setTranslationY(translation);
+            }
+            mCurrentView.setBackgroundAlpha(alpha);
+            if (ALLOWS_DRAG && alpha < 0.7) {
+                setOnDragListener(mDragListener);
+                DragShadowBuilder shadowBuilder = new DragShadowBuilder(mCurrentView);
+                mCurrentView.startDrag(null, shadowBuilder, mCurrentView, 0);
+                mStartedDrag = true;
+            }
+        }
+        if (!mExited && mCurrentView != null
+                && mCurrentView.getBackgroundAlpha() > mDeleteSlope
+                && event.getActionMasked() == MotionEvent.ACTION_UP
+                && System.currentTimeMillis() - mTouchTime < mMaxTouchDelay) {
+            FilterRepresentation representation = mCurrentView.getState().getFilterRepresentation();
+            mCurrentView.setSelected(true);
+            if (representation != MasterImage.getImage().getCurrentFilterRepresentation()) {
+                FilterShowActivity activity = (FilterShowActivity) getContext();
+                activity.showRepresentation(representation);
+                mCurrentView.setSelected(false);
+            }
+        }
+        if (event.getActionMasked() == MotionEvent.ACTION_UP
+                || (!mStartedDrag && event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
+            checkEndState();
+            if (mCurrentView != null) {
+                FilterRepresentation representation = mCurrentView.getState().getFilterRepresentation();
+                if (representation.getEditorId() == ImageOnlyEditor.ID) {
+                    mCurrentView.setSelected(false);
+                }
+            }
+        }
+        return true;
+    }
+
+    public void checkEndState() {
+        mTouchPoint = null;
+        mTouchTime = 0;
+        if (mExited || mCurrentView.getBackgroundAlpha() < mDeleteSlope) {
+            int origin = findChild(mCurrentView);
+            if (origin != -1) {
+                State current = mAdapter.getItem(origin);
+                FilterRepresentation currentRep = MasterImage.getImage().getCurrentFilterRepresentation();
+                FilterRepresentation removedRep = current.getFilterRepresentation();
+                mAdapter.remove(current);
+                fillContent(true);
+                if (currentRep != null && removedRep != null
+                        && currentRep.getFilterClass() == removedRep.getFilterClass()) {
+                    FilterShowActivity activity = (FilterShowActivity) getContext();
+                    activity.backToMain();
+                    return;
+                }
+            }
+        } else {
+            mCurrentView.setBackgroundAlpha(1.0f);
+            mCurrentView.setTranslationX(0);
+            mCurrentView.setTranslationY(0);
+        }
+        if (mCurrentSelectedView != null) {
+            mCurrentSelectedView.invalidate();
+        }
+        if (mCurrentView != null) {
+            mCurrentView.invalidate();
+        }
+        mCurrentView = null;
+        mExited = false;
+        mStartedDrag = false;
+    }
+
+    public View findChildAt(int x, int y) {
+        Rect frame = new Rect();
+        int scrolledXInt = getScrollX() + x;
+        int scrolledYInt = getScrollY() + y;
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            child.getHitRect(frame);
+            if (frame.contains(scrolledXInt, scrolledYInt)) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    public int findChild(View view) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child == view) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public StateView getCurrentView() {
+        return mCurrentView;
+    }
+
+    public void setCurrentView(View currentView) {
+        mCurrentView = (StateView) currentView;
+    }
+
+    public void setExited(boolean value) {
+        mExited = value;
+    }
+
+    public Point getTouchPoint() {
+        return mTouchPoint;
+    }
+
+    public Adapter getAdapter() {
+        return mAdapter;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/state/StateView.java b/src/com/android/gallery3d/filtershow/state/StateView.java
new file mode 100644
index 0000000..9353a43
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/state/StateView.java
@@ -0,0 +1,290 @@
+/*
+ * 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.gallery3d.filtershow.state;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+
+public class StateView extends View {
+
+    private static final String LOGTAG = "StateView";
+    private Path mPath = new Path();
+    private Paint mPaint = new Paint();
+
+    public static int DEFAULT = 0;
+    public static int BEGIN = 1;
+    public static int END = 2;
+
+    public static int UP = 1;
+    public static int DOWN = 2;
+    public static int LEFT = 3;
+    public static int RIGHT = 4;
+
+    private int mType = DEFAULT;
+    private float mAlpha = 1.0f;
+    private String mText = "Default";
+    private float mTextSize = 32;
+    private static int sMargin = 16;
+    private static int sArrowHeight = 16;
+    private static int sArrowWidth = 8;
+    private int mOrientation = LinearLayout.VERTICAL;
+    private int mDirection = DOWN;
+    private boolean mDuplicateButton;
+    private State mState;
+
+    private int mEndsBackgroundColor;
+    private int mEndsTextColor;
+    private int mBackgroundColor;
+    private int mTextColor;
+    private int mSelectedBackgroundColor;
+    private int mSelectedTextColor;
+    private Rect mTextBounds = new Rect();
+
+    public StateView(Context context) {
+        this(context, DEFAULT);
+    }
+
+    public StateView(Context context, int type) {
+        super(context);
+        mType = type;
+        Resources res = getResources();
+        mEndsBackgroundColor = res.getColor(R.color.filtershow_stateview_end_background);
+        mEndsTextColor = res.getColor(R.color.filtershow_stateview_end_text);
+        mBackgroundColor = res.getColor(R.color.filtershow_stateview_background);
+        mTextColor = res.getColor(R.color.filtershow_stateview_text);
+        mSelectedBackgroundColor = res.getColor(R.color.filtershow_stateview_selected_background);
+        mSelectedTextColor = res.getColor(R.color.filtershow_stateview_selected_text);
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    public void setText(String text) {
+        mText = text;
+        invalidate();
+    }
+
+    public void setType(int type) {
+        mType = type;
+        invalidate();
+    }
+
+    @Override
+    public void setSelected(boolean value) {
+        super.setSelected(value);
+        if (!value) {
+            mDuplicateButton = false;
+        }
+        invalidate();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            ViewParent parent = getParent();
+            if (parent instanceof PanelTrack) {
+                ((PanelTrack) getParent()).onTouch(event, this);
+            }
+            if (mType == BEGIN) {
+                MasterImage.getImage().setShowsOriginal(true);
+            }
+        }
+        if (event.getActionMasked() == MotionEvent.ACTION_UP
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+            MasterImage.getImage().setShowsOriginal(false);
+        }
+        return true;
+    }
+
+    public void drawText(Canvas canvas) {
+        if (mText == null) {
+            return;
+        }
+        mPaint.reset();
+        if (isSelected()) {
+            mPaint.setColor(mSelectedTextColor);
+        } else {
+            mPaint.setColor(mTextColor);
+        }
+        if (mType == BEGIN) {
+            mPaint.setColor(mEndsTextColor);
+        }
+        mPaint.setTypeface(Typeface.DEFAULT_BOLD);
+        mPaint.setAntiAlias(true);
+        mPaint.setTextSize(mTextSize);
+        mPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
+        int x = (canvas.getWidth() - mTextBounds.width()) / 2;
+        int y = mTextBounds.height() + (canvas.getHeight() - mTextBounds.height()) / 2;
+        canvas.drawText(mText, x, y, mPaint);
+    }
+
+    public void onDraw(Canvas canvas) {
+        canvas.drawARGB(0, 0, 0, 0);
+        mPaint.reset();
+        mPath.reset();
+
+        float w = canvas.getWidth();
+        float h = canvas.getHeight();
+        float r = sArrowHeight;
+        float d = sArrowWidth;
+
+        if (mOrientation == LinearLayout.HORIZONTAL) {
+            drawHorizontalPath(w, h, r, d);
+        } else {
+            if (mDirection == DOWN) {
+                drawVerticalDownPath(w, h, r, d);
+            } else {
+                drawVerticalPath(w, h, r, d);
+            }
+        }
+
+        if (mType == DEFAULT || mType == END) {
+            if (mDuplicateButton) {
+                mPaint.setARGB(255, 200, 0, 0);
+            } else if (isSelected()) {
+                mPaint.setColor(mSelectedBackgroundColor);
+            } else {
+                mPaint.setColor(mBackgroundColor);
+            }
+        } else {
+            mPaint.setColor(mEndsBackgroundColor);
+        }
+        canvas.drawPath(mPath, mPaint);
+        drawText(canvas);
+    }
+
+    private void drawHorizontalPath(float w, float h, float r, float d) {
+        mPath.moveTo(0, 0);
+        if (mType == END) {
+            mPath.lineTo(w, 0);
+            mPath.lineTo(w, h);
+        } else {
+            mPath.lineTo(w - d, 0);
+            mPath.lineTo(w - d, r);
+            mPath.lineTo(w, r + d);
+            mPath.lineTo(w - d, r + d + r);
+            mPath.lineTo(w - d, h);
+        }
+        mPath.lineTo(0, h);
+        if (mType != BEGIN) {
+            mPath.lineTo(0, r + d + r);
+            mPath.lineTo(d, r + d);
+            mPath.lineTo(0, r);
+        }
+        mPath.close();
+    }
+
+    private void drawVerticalPath(float w, float h, float r, float d) {
+        if (mType == BEGIN) {
+            mPath.moveTo(0, 0);
+            mPath.lineTo(w, 0);
+        } else {
+            mPath.moveTo(0, d);
+            mPath.lineTo(r, d);
+            mPath.lineTo(r + d, 0);
+            mPath.lineTo(r + d + r, d);
+            mPath.lineTo(w, d);
+        }
+        mPath.lineTo(w, h);
+        if (mType != END) {
+            mPath.lineTo(r + d + r, h);
+            mPath.lineTo(r + d, h - d);
+            mPath.lineTo(r, h);
+        }
+        mPath.lineTo(0, h);
+        mPath.close();
+    }
+
+    private void drawVerticalDownPath(float w, float h, float r, float d) {
+        mPath.moveTo(0, 0);
+        if (mType != BEGIN) {
+            mPath.lineTo(r, 0);
+            mPath.lineTo(r + d, d);
+            mPath.lineTo(r + d + r, 0);
+        }
+        mPath.lineTo(w, 0);
+
+        if (mType != END) {
+            mPath.lineTo(w, h - d);
+
+            mPath.lineTo(r + d + r, h - d);
+            mPath.lineTo(r + d, h);
+            mPath.lineTo(r, h - d);
+
+            mPath.lineTo(0, h - d);
+        } else {
+            mPath.lineTo(w, h);
+            mPath.lineTo(0, h);
+        }
+
+        mPath.close();
+    }
+
+    public void setBackgroundAlpha(float alpha) {
+        if (mType == BEGIN) {
+            return;
+        }
+        mAlpha = alpha;
+        setAlpha(alpha);
+        invalidate();
+    }
+
+    public float getBackgroundAlpha() {
+        return mAlpha;
+    }
+
+    public void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    public void setDuplicateButton(boolean b) {
+        mDuplicateButton = b;
+        invalidate();
+    }
+
+    public State getState() {
+        return mState;
+    }
+
+    public void setState(State state) {
+        mState = state;
+        mText = mState.getText().toUpperCase();
+        mType = mState.getType();
+        invalidate();
+    }
+
+    public void resetPosition() {
+        setTranslationX(0);
+        setTranslationY(0);
+        setBackgroundAlpha(1.0f);
+    }
+
+    public boolean isDraggable() {
+        return mState.isDraggable();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/BitmapTask.java b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java
new file mode 100644
index 0000000..62801c1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.tools;
+
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+
+/**
+ * Asynchronous task filtering or doign I/O with bitmaps.
+ */
+public class BitmapTask <T> extends AsyncTask<T, Void, Bitmap> {
+
+    private Callbacks<T> mCallbacks;
+    private static final String LOGTAG = "BitmapTask";
+
+    public BitmapTask(Callbacks<T> callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    @Override
+    protected Bitmap doInBackground(T... params) {
+        if (params == null || mCallbacks == null) {
+            return null;
+        }
+        return mCallbacks.onExecute(params[0]);
+    }
+
+    @Override
+    protected void onPostExecute(Bitmap result) {
+        if (mCallbacks == null) {
+            return;
+        }
+        mCallbacks.onComplete(result);
+    }
+
+    @Override
+    protected void onCancelled() {
+        if (mCallbacks == null) {
+            return;
+        }
+        mCallbacks.onCancel();
+    }
+
+    /**
+     * Callbacks for the asynchronous task.
+     */
+    public interface Callbacks<P> {
+        void onComplete(Bitmap result);
+
+        void onCancel();
+
+        Bitmap onExecute(P param);
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/IconFactory.java b/src/com/android/gallery3d/filtershow/tools/IconFactory.java
new file mode 100644
index 0000000..ccc49e1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/IconFactory.java
@@ -0,0 +1,108 @@
+/*
+ * 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.gallery3d.filtershow.tools;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * A factory class for producing bitmaps to use as UI icons.
+ */
+public class IconFactory {
+
+    /**
+     * Builds an icon with the dimensions iconWidth:iconHeight. If scale is set
+     * the source image is stretched to fit within the given dimensions;
+     * otherwise, the source image is cropped to the proper aspect ratio.
+     *
+     * @param sourceImage image to create an icon from.
+     * @param iconWidth width of the icon bitmap.
+     * @param iconHeight height of the icon bitmap.
+     * @param scale if true, stretch sourceImage to fit the icon dimensions.
+     * @return an icon bitmap with the dimensions iconWidth:iconHeight.
+     */
+    public static Bitmap createIcon(Bitmap sourceImage, int iconWidth, int iconHeight,
+            boolean scale) {
+        if (sourceImage == null) {
+            throw new IllegalArgumentException("Null argument to buildIcon");
+        }
+
+        int sourceWidth = sourceImage.getWidth();
+        int sourceHeight = sourceImage.getHeight();
+
+        if (sourceWidth == 0 || sourceHeight == 0 || iconWidth == 0 || iconHeight == 0) {
+            throw new IllegalArgumentException("Bitmap with dimension 0 used as input");
+        }
+
+        Bitmap icon = Bitmap.createBitmap(iconWidth, iconHeight,
+                Bitmap.Config.ARGB_8888);
+        drawIcon(icon, sourceImage, scale);
+        return icon;
+    }
+
+    /**
+     * Draws an icon in the destination bitmap. If scale is set the source image
+     * is stretched to fit within the destination dimensions; otherwise, the
+     * source image is cropped to the proper aspect ratio.
+     *
+     * @param dest bitmap into which to draw the icon.
+     * @param sourceImage image to create an icon from.
+     * @param scale if true, stretch sourceImage to fit the destination.
+     */
+    public static void drawIcon(Bitmap dest, Bitmap sourceImage, boolean scale) {
+        if (dest == null || sourceImage == null) {
+            throw new IllegalArgumentException("Null argument to buildIcon");
+        }
+
+        int sourceWidth = sourceImage.getWidth();
+        int sourceHeight = sourceImage.getHeight();
+        int iconWidth = dest.getWidth();
+        int iconHeight = dest.getHeight();
+
+        if (sourceWidth == 0 || sourceHeight == 0 || iconWidth == 0 || iconHeight == 0) {
+            throw new IllegalArgumentException("Bitmap with dimension 0 used as input");
+        }
+
+        Rect destRect = new Rect(0, 0, iconWidth, iconHeight);
+        Canvas canvas = new Canvas(dest);
+
+        Rect srcRect = null;
+        if (scale) {
+            // scale image to fit in icon (stretches if aspect isn't the same)
+            srcRect = new Rect(0, 0, sourceWidth, sourceHeight);
+        } else {
+            // crop image to aspect ratio iconWidth:iconHeight
+            float wScale = sourceWidth / (float) iconWidth;
+            float hScale = sourceHeight / (float) iconHeight;
+            float s = Math.min(hScale, wScale);
+
+            float iw = iconWidth * s;
+            float ih = iconHeight * s;
+
+            float borderW = (sourceWidth - iw) / 2.0f;
+            float borderH = (sourceHeight - ih) / 2.0f;
+            RectF rec = new RectF(borderW, borderH, borderW + iw, borderH + ih);
+            srcRect = new Rect();
+            rec.roundOut(srcRect);
+        }
+
+        canvas.drawBitmap(sourceImage, srcRect, destRect, new Paint());
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index 7200db3..6255f31 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -22,8 +22,6 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Bitmap.CompressFormat;
-import android.media.ExifInterface;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Environment;
@@ -31,57 +29,28 @@
 import android.provider.MediaStore.Images.ImageColumns;
 import android.util.Log;
 
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.filtershow.cache.CachingPipeline;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 import com.android.gallery3d.util.XmpUtilHelper;
 
-import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.text.SimpleDateFormat;
 import java.util.Date;
+import java.util.TimeZone;
 
 /**
  * Asynchronous task for saving edited photo as a new copy.
  */
 public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
 
-
     private static final String LOGTAG = "SaveCopyTask";
-    private static final int DEFAULT_COMPRESS_QUALITY = 95;
-    private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
-
-    /**
-     * Saves the bitmap in the final destination
-     */
-    public static void saveBitmap(Bitmap bitmap, File destination, Object xmp) {
-        OutputStream os = null;
-        try {
-            os = new FileOutputStream(destination);
-            bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os);
-        } catch (FileNotFoundException e) {
-            Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath());
-        } finally {
-            closeStream(os);
-        }
-        if (xmp != null) {
-            XmpUtilHelper.writeXMPMeta(destination.getAbsolutePath(), xmp);
-        }
-    }
-
-    private static void closeStream(Closeable stream) {
-        if (stream != null) {
-            try {
-                stream.close();
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
 
     /**
      * Callback for the completed asynchronous task.
@@ -122,10 +91,11 @@
         File saveDirectory = getSaveDirectory(context, sourceUri);
         if ((saveDirectory == null) || !saveDirectory.canWrite()) {
             saveDirectory = new File(Environment.getExternalStorageDirectory(),
-                    DEFAULT_SAVE_DIRECTORY);
+                    ImageLoader.DEFAULT_SAVE_DIRECTORY);
         }
         // Create the directory if it doesn't exist
-        if (!saveDirectory.exists()) saveDirectory.mkdirs();
+        if (!saveDirectory.exists())
+            saveDirectory.mkdirs();
         return saveDirectory;
     }
 
@@ -135,82 +105,59 @@
         return new File(saveDirectory, filename + ".JPG");
     }
 
-    private Bitmap loadMutableBitmap() throws FileNotFoundException {
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't
-        // exist)
-        options.inMutable = true;
-
-        InputStream is = context.getContentResolver().openInputStream(sourceUri);
-        Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
-        int orientation = ImageLoader.getOrientation(context, sourceUri);
-        bitmap = ImageLoader.rotateToPortrait(bitmap, orientation);
-        return bitmap;
+    public Object getPanoramaXMPData(Uri source, ImagePreset preset) {
+        Object xmp = null;
+        if (preset.isPanoramaSafe()) {
+            InputStream is = null;
+            try {
+                is = context.getContentResolver().openInputStream(source);
+                xmp = XmpUtilHelper.extractXMPMeta(is);
+            } catch (FileNotFoundException e) {
+                Log.w(LOGTAG, "Failed to get XMP data from image: ", e);
+            } finally {
+                Utils.closeSilently(is);
+            }
+        }
+        return xmp;
     }
 
-    private static final String[] COPY_EXIF_ATTRIBUTES = new String[] {
-        ExifInterface.TAG_APERTURE,
-        ExifInterface.TAG_DATETIME,
-        ExifInterface.TAG_EXPOSURE_TIME,
-        ExifInterface.TAG_FLASH,
-        ExifInterface.TAG_FOCAL_LENGTH,
-        ExifInterface.TAG_GPS_ALTITUDE,
-        ExifInterface.TAG_GPS_ALTITUDE_REF,
-        ExifInterface.TAG_GPS_DATESTAMP,
-        ExifInterface.TAG_GPS_LATITUDE,
-        ExifInterface.TAG_GPS_LATITUDE_REF,
-        ExifInterface.TAG_GPS_LONGITUDE,
-        ExifInterface.TAG_GPS_LONGITUDE_REF,
-        ExifInterface.TAG_GPS_PROCESSING_METHOD,
-        ExifInterface.TAG_GPS_DATESTAMP,
-        ExifInterface.TAG_ISO,
-        ExifInterface.TAG_MAKE,
-        ExifInterface.TAG_MODEL,
-        ExifInterface.TAG_WHITE_BALANCE,
-    };
-
-    private static void copyExif(String sourcePath, String destPath) {
-        try {
-            ExifInterface source = new ExifInterface(sourcePath);
-            ExifInterface dest = new ExifInterface(destPath);
-            boolean needsSave = false;
-            for (String tag : COPY_EXIF_ATTRIBUTES) {
-                String value = source.getAttribute(tag);
-                if (value != null) {
-                    needsSave = true;
-                    dest.setAttribute(tag, value);
-                }
-            }
-            if (needsSave) {
-                dest.saveAttributes();
-            }
-        } catch (IOException ex) {
-            Log.w(LOGTAG, "Failed to copy exif metadata", ex);
+    public boolean putPanoramaXMPData(File file, Object xmp) {
+        if (xmp != null) {
+            return XmpUtilHelper.writeXMPMeta(file.getAbsolutePath(), xmp);
         }
+        return false;
     }
 
-    private void copyExif(Uri sourceUri, String destPath) {
-        if (ContentResolver.SCHEME_FILE.equals(sourceUri.getScheme())) {
-            copyExif(sourceUri.getPath(), destPath);
-            return;
-        }
-
-        final String[] PROJECTION = new String[] {
-                ImageColumns.DATA
-        };
-        try {
-            Cursor c = context.getContentResolver().query(sourceUri, PROJECTION,
-                    null, null, null);
-            if (c.moveToFirst()) {
-                String path = c.getString(0);
-                if (new File(path).exists()) {
-                    copyExif(path, destPath);
-                }
+    public ExifInterface getExifData(Uri source) {
+        ExifInterface exif = new ExifInterface();
+        String mimeType = context.getContentResolver().getType(sourceUri);
+        if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) {
+            InputStream inStream = null;
+            try {
+                inStream = context.getContentResolver().openInputStream(source);
+                exif.readExif(inStream);
+            } catch (FileNotFoundException e) {
+                Log.w(LOGTAG, "Cannot find file: " + source, e);
+            } catch (IOException e) {
+                Log.w(LOGTAG, "Cannot read exif for: " + source, e);
+            } finally {
+                Utils.closeSilently(inStream);
             }
-            c.close();
-        } catch (Exception e) {
-            Log.w(LOGTAG, "Failed to copy exif", e);
         }
+        return exif;
+    }
+
+    public boolean putExifData(File file, ExifInterface exif, Bitmap image) {
+        boolean ret = false;
+        try {
+            exif.writeExif(image, file.getAbsolutePath());
+            ret = true;
+        } catch (FileNotFoundException e) {
+            Log.w(LOGTAG, "File not found: " + file.getAbsolutePath(), e);
+        } catch (IOException e) {
+            Log.w(LOGTAG, "Could not write exif: ", e);
+        }
+        return ret;
     }
 
     /**
@@ -219,32 +166,55 @@
     @Override
     protected Uri doInBackground(ImagePreset... params) {
         // TODO: Support larger dimensions for photo saving.
-        if (params[0] == null) {
+        if (params[0] == null || sourceUri == null) {
             return null;
         }
-
         ImagePreset preset = params[0];
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        Uri uri = null;
+        boolean noBitmap = true;
+        int num_tries = 0;
+        // Stopgap fix for low-memory devices.
+        while (noBitmap) {
+            try {
+                // Try to do bitmap operations, downsample if low-memory
+                Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options);
+                if (bitmap == null) {
+                    return null;
+                }
+                CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Saving");
+                bitmap = pipeline.renderFinalImage(bitmap, preset);
 
-        try {
-            Bitmap bitmap = preset.apply(loadMutableBitmap());
+                Object xmp = getPanoramaXMPData(sourceUri, preset);
+                ExifInterface exif = getExifData(sourceUri);
 
-            Object xmp = null;
-            InputStream is = null;
-            if (preset.isPanoramaSafe()) {
-                is = context.getContentResolver().openInputStream(sourceUri);
-                xmp =  XmpUtilHelper.extractXMPMeta(is);
+                // Set tags
+                long time = System.currentTimeMillis();
+                exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, time,
+                        TimeZone.getDefault());
+                exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
+                        ExifInterface.Orientation.TOP_LEFT));
+
+                // Remove old thumbnail
+                exif.removeCompressedThumbnail();
+
+                // If we succeed in writing the bitmap as a jpeg, return a uri.
+                if (putExifData(this.destinationFile, exif, bitmap)) {
+                    putPanoramaXMPData(this.destinationFile, xmp);
+                    uri = insertContent(context, sourceUri, this.destinationFile, saveFileName,
+                            time);
+                }
+                noBitmap = false;
+            } catch (java.lang.OutOfMemoryError e) {
+                // Try 5 times before failing for good.
+                if (++num_tries >= 5) {
+                    throw e;
+                }
+                System.gc();
+                options.inSampleSize *= 2;
             }
-            saveBitmap(bitmap, this.destinationFile, xmp);
-            copyExif(sourceUri, destinationFile.getAbsolutePath());
-
-            Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName);
-            bitmap.recycle();
-            return uri;
-
-        } catch (FileNotFoundException ex) {
-            Log.w(LOGTAG, "Failed to save image!", ex);
-            return null;
         }
+        return uri;
     }
 
     @Override
@@ -291,16 +261,17 @@
     /**
      * Insert the content (saved file) with proper source photo properties.
      */
-    public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName) {
-        long now = System.currentTimeMillis() / 1000;
+    public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName,
+            long time) {
+        time /= 1000;
 
         final ContentValues values = new ContentValues();
         values.put(Images.Media.TITLE, saveFileName);
         values.put(Images.Media.DISPLAY_NAME, file.getName());
         values.put(Images.Media.MIME_TYPE, "image/jpeg");
-        values.put(Images.Media.DATE_TAKEN, now);
-        values.put(Images.Media.DATE_MODIFIED, now);
-        values.put(Images.Media.DATE_ADDED, now);
+        values.put(Images.Media.DATE_TAKEN, time);
+        values.put(Images.Media.DATE_MODIFIED, time);
+        values.put(Images.Media.DATE_ADDED, time);
         values.put(Images.Media.ORIENTATION, 0);
         values.put(Images.Media.DATA, file.getAbsolutePath());
         values.put(Images.Media.SIZE, file.length());
@@ -312,20 +283,20 @@
         querySource(context, sourceUri, projection,
                 new ContentResolverQueryCallback() {
 
-            @Override
-            public void onCursorResult(Cursor cursor) {
-                values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
+                    @Override
+                    public void onCursorResult(Cursor cursor) {
+                        values.put(Images.Media.DATE_TAKEN, cursor.getLong(0));
 
-                double latitude = cursor.getDouble(1);
-                double longitude = cursor.getDouble(2);
-                // TODO: Change || to && after the default location issue is
-                // fixed.
-                if ((latitude != 0f) || (longitude != 0f)) {
-                    values.put(Images.Media.LATITUDE, latitude);
-                    values.put(Images.Media.LONGITUDE, longitude);
-                }
-            }
-        });
+                        double latitude = cursor.getDouble(1);
+                        double longitude = cursor.getDouble(2);
+                        // TODO: Change || to && after the default location
+                        // issue is fixed.
+                        if ((latitude != 0f) || (longitude != 0f)) {
+                            values.put(Images.Media.LATITUDE, latitude);
+                            values.put(Images.Media.LONGITUDE, longitude);
+                        }
+                    }
+                });
 
         return context.getContentResolver().insert(
                 Images.Media.EXTERNAL_CONTENT_URI, values);
diff --git a/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java
new file mode 100644
index 0000000..96126c5
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.ui;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.cache.RenderingRequest;
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.category.Action;
+import com.android.gallery3d.filtershow.category.CategoryAdapter;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.imageshow.GeometryListener;
+import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.tools.IconFactory;
+
+// TODO: merge back IconButton and FilterIconButton?
+public class FilterIconButton extends IconButton implements View.OnClickListener,
+        RenderingRequestCaller, GeometryListener {
+    private static final String LOGTAG = "FilterIconButton";
+    private Bitmap mOverlayBitmap = null;
+    private boolean mOverlayOnly = false;
+    private FilterRepresentation mFilterRepresentation = null;
+    private Bitmap mIconBitmap = null;
+    private Action mAction;
+    private Paint mSelectPaint;
+    private int mSelectStroke;
+    private CategoryAdapter mAdapter;
+    public FilterIconButton(Context context) {
+        super(context);
+    }
+
+    public FilterIconButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public FilterIconButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setup(String text, LinearLayout parent, CategoryAdapter adapter) {
+        mAdapter = adapter;
+        setText(text);
+        setContentDescription(text);
+        super.setOnClickListener(this);
+        Resources res = getContext().getResources();
+        MasterImage.getImage().addGeometryListener(this);
+        mSelectStroke = res.getDimensionPixelSize(R.dimen.thumbnail_margin);
+        mSelectPaint = new Paint();
+        mSelectPaint.setStyle(Paint.Style.FILL);
+        mSelectPaint.setColor(res.getColor(R.color.filtershow_category_selection));
+        invalidate();
+    }
+
+    @Override
+    public void onClick(View v) {
+        FilterShowActivity activity = (FilterShowActivity) getContext();
+        activity.showRepresentation(mFilterRepresentation);
+        mAdapter.setSelected(v);
+    }
+
+    public FilterRepresentation getFilterRepresentation() {
+        return mFilterRepresentation;
+    }
+
+    public void setAction(Action action) {
+        mAction = action;
+        if (action == null) {
+            return;
+        }
+        if (mAction.getPortraitImage() != null) {
+            mIconBitmap = mAction.getPortraitImage();
+            setIcon(mIconBitmap);
+        }
+        setFilterRepresentation(mAction.getRepresentation());
+    }
+
+    private void setFilterRepresentation(FilterRepresentation filterRepresentation) {
+        mFilterRepresentation = filterRepresentation;
+        if (mFilterRepresentation != null && mFilterRepresentation.getOverlayId() != 0) {
+            if (mAction.getOverlayBitmap() == null) {
+                mOverlayBitmap = BitmapFactory.decodeResource(getResources(),
+                    mFilterRepresentation.getOverlayId());
+                mAction.setOverlayBitmap(mOverlayBitmap);
+            } else {
+                mOverlayBitmap = mAction.getOverlayBitmap();
+            }
+        }
+        mOverlayOnly = mFilterRepresentation.getOverlayOnly();
+        if (mOverlayOnly) {
+            assert(mOverlayBitmap != null);
+            setIcon(mOverlayBitmap);
+        }
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mIconBitmap == null && !mOverlayOnly) {
+            postNewIconRenderRequest();
+        } else {
+            super.onDraw(canvas);
+        }
+        if (mAdapter.isSelected(this)) {
+            Drawable iconDrawable = getCompoundDrawables()[1];
+            if (iconDrawable != null) {
+                canvas.save();
+                int padding = getCompoundDrawablePadding();
+                canvas.translate(getScrollX() + padding + getPaddingLeft() - mSelectStroke - 1,
+                        getScrollY() + padding + getPaddingTop() - mSelectStroke - 1);
+                Rect r = iconDrawable.getBounds();
+                SelectionRenderer.drawSelection(canvas, r.left, r.top,
+                        r.right + 2 * mSelectStroke + 2, r.bottom + 2 * mSelectStroke + 2,
+                        mSelectStroke, mSelectPaint);
+                canvas.restore();
+            }
+        }
+    }
+
+    @Override
+    public void available(RenderingRequest request) {
+        Bitmap bmap = request.getBitmap();
+        if (bmap == null) {
+            return;
+        }
+        if (mOverlayOnly) {
+            setIcon(mOverlayBitmap);
+        } else {
+            mIconBitmap = bmap;
+            if (mOverlayBitmap != null) {
+                // Draw overlay bitmap over icon
+                IconFactory.drawIcon(mIconBitmap, mOverlayBitmap, false);
+            }
+            setIcon(mIconBitmap);
+            if (mAction != null) {
+                mAction.setPortraitImage(mIconBitmap);
+            }
+        }
+    }
+
+    @Override
+    public void geometryChanged() {
+        if (mOverlayOnly) {
+            return;
+        }
+        mIconBitmap = null;
+        invalidate();
+    }
+
+    private void postNewIconRenderRequest() {
+        Bitmap dst = MasterImage.getImage().getThumbnailBitmap();
+        if (dst != null && mAction != null) {
+            ImagePreset mPreset = new ImagePreset();
+            mPreset.addFilter(mFilterRepresentation);
+
+            GeometryMetadata geometry = mPreset.mGeoData;
+            RectF bound = new RectF(0, 0, dst.getWidth(), dst.getHeight());
+            geometry.setCropBounds(bound);
+            geometry.setPhotoBounds(bound);
+
+            RenderingRequest.post(dst.copy(Bitmap.Config.ARGB_8888, true),
+                    mPreset, RenderingRequest.ICON_RENDERING, this);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
index c717b6e..c1e4109 100644
--- a/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
+++ b/src/com/android/gallery3d/filtershow/ui/FramedTextButton.java
@@ -37,8 +37,6 @@
     private static int mTrianglePadding = 2;
     private static int mTriangleSize = 30;
 
-    private Context mContext = null;
-
     public static void setTextSize(int value) {
         mTextSize = value;
     }
@@ -63,28 +61,34 @@
     public void setTextFrom(int itemId) {
         switch (itemId) {
             case R.id.curve_menu_rgb: {
-                setText(mContext.getString(R.string.curves_channel_rgb));
+                setText(getContext().getString(R.string.curves_channel_rgb));
                 break;
             }
             case R.id.curve_menu_red: {
-                setText(mContext.getString(R.string.curves_channel_red));
+                setText(getContext().getString(R.string.curves_channel_red));
                 break;
             }
             case R.id.curve_menu_green: {
-                setText(mContext.getString(R.string.curves_channel_green));
+                setText(getContext().getString(R.string.curves_channel_green));
                 break;
             }
             case R.id.curve_menu_blue: {
-                setText(mContext.getString(R.string.curves_channel_blue));
+                setText(getContext().getString(R.string.curves_channel_blue));
                 break;
             }
         }
         invalidate();
     }
 
+    public FramedTextButton(Context context) {
+        this(context, null);
+    }
+
     public FramedTextButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mContext = context;
+        if (attrs == null) {
+            return;
+        }
         TypedArray a = getContext().obtainStyledAttributes(
                 attrs, R.styleable.ImageButtonTitle);
 
diff --git a/src/com/android/gallery3d/filtershow/ui/IconButton.java b/src/com/android/gallery3d/filtershow/ui/IconButton.java
new file mode 100644
index 0000000..e7087bd
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/IconButton.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+import com.android.gallery3d.filtershow.tools.IconFactory;
+import com.android.photos.data.GalleryBitmapPool;
+
+/**
+ * Class of buttons with both an image icon and text.
+ */
+public class IconButton extends Button {
+
+    private Bitmap mImageMirror = null;
+    private Bitmap mIcon = null;
+
+    private boolean stale_icon = true;
+
+    public IconButton(Context context) {
+        this(context, null);
+    }
+
+    public IconButton(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public IconButton(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        BitmapDrawable ic = (BitmapDrawable) getCompoundDrawables()[1];
+
+        if (ic != null) {
+            mImageMirror = ic.getBitmap();
+        }
+    }
+
+    /**
+     * Set the image that the button icon will use.  The image bitmap will be scaled
+     * and cropped into the largest bitmap with dimensions given by getGoodIconSideSize()
+     * that will fit cleanly within the IconButton's layout.
+     *
+     * @param image image that icon will be set to before next draw.
+     */
+    public void setIcon(Bitmap image) {
+        mImageMirror = image;
+        stale_icon = true;
+        invalidate();
+    }
+
+    /**
+     * Finds a side lengths for the icon that fits within the button.
+     * Only call after layout.  The default implementation returns the best
+     * side lengths for a square icon.
+     * <p>
+     * Override this to make non-square icons or icons with different padding
+     * constraints.
+     *
+     * @return  an array of ints representing the icon dimensions [ width, height ]
+     */
+    protected int[] getGoodIconSideSize() {
+        Paint p = getPaint();
+        Rect bounds = new Rect();
+        // find text bounds
+        String s = getText().toString();
+        p.getTextBounds(s, 0, s.length(), bounds);
+
+        int inner_padding = 2 * getCompoundDrawablePadding();
+
+        // find total vertical space available for the icon
+        int vert = getHeight() - getPaddingTop() - getPaddingBottom() - bounds.height()
+                - inner_padding;
+
+        // find total horizontal space available for the icon
+        int horiz = getWidth() - getPaddingLeft() - getPaddingRight() - inner_padding;
+
+        int defaultSize = Math.min(vert, horiz);
+        return new int[] { defaultSize, defaultSize };
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+        if (w != oldw || h != oldh) {
+            stale_icon = true;
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (stale_icon && mImageMirror != null && mImageMirror.getHeight() > 0
+                && mImageMirror.getWidth() > 0) {
+            stale_icon = !makeAndSetIcon(mImageMirror);
+        }
+        super.onDraw(canvas);
+    }
+
+    // Internal methods
+
+    /**
+     * Creates and sets button icon. Only call after layout.
+     *
+     * @param image bitmap to use as icon
+     */
+    private boolean makeAndSetIcon(Bitmap image) {
+        int[] sizes = getGoodIconSideSize();
+        if (sizes != null && sizes.length >= 2 && sizes[0] > 0 && sizes[1] > 0) {
+            return setImageIcon(makeImageIcon(image, sizes[0], sizes[1]));
+        }
+        return false;
+    }
+
+    /**
+     * Sets icon.
+     *
+     * @param image bitmap to set the icon to.
+     */
+    private boolean setImageIcon(Bitmap image) {
+        if (image == null) {
+            return false;
+        }
+        if(mIcon != null && mIcon.getConfig() == Bitmap.Config.ARGB_8888) {
+            GalleryBitmapPool.getInstance().put(mIcon);
+            mIcon = null;
+        }
+        mIcon = image;
+        this.setCompoundDrawablesWithIntrinsicBounds(null,
+                new BitmapDrawable(getResources(), mIcon), null, null);
+        return true;
+    }
+
+    /**
+     * Generate an icon bitmap from a given bitmap.
+     *
+     * @param image bitmap to use as button icon
+     * @param width icon width
+     * @param height icon height
+     * @return the scaled/cropped icon bitmap
+     */
+    private Bitmap makeImageIcon(Bitmap image, int width, int height) {
+        if (image == null || image.getHeight() < 1 || image.getWidth() < 1 ||
+                width < 1 || height < 1) {
+            throw new IllegalArgumentException("input is null, or has invalid dimensions");
+        }
+        Bitmap icon = null;
+        icon = GalleryBitmapPool.getInstance().get(width, height);
+        if (icon == null) {
+            icon = IconFactory.createIcon(image, width, height, false);
+        } else {
+            assert(icon.getWidth() == width && icon.getHeight() == height);
+            icon.eraseColor(Color.TRANSPARENT);
+            IconFactory.drawIcon(icon, image, false);
+        }
+        return icon;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java b/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java
deleted file mode 100644
index bb37751..0000000
--- a/src/com/android/gallery3d/filtershow/ui/ImageButtonTitle.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-import android.widget.ImageButton;
-
-import com.android.gallery3d.R;
-
-public class ImageButtonTitle extends ImageButton {
-    private static final String LOGTAG = "ImageButtonTitle";
-    private String mText = null;
-    private static int mTextSize = 24;
-    private static int mTextPadding = 20;
-    private static Paint gPaint = new Paint();
-
-    public static void setTextSize(int value) {
-        mTextSize = value;
-    }
-
-    public static void setTextPadding(int value) {
-        mTextPadding = value;
-    }
-
-    public void setText(String text) {
-        mText = text;
-    }
-
-    public ImageButtonTitle(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        TypedArray a = getContext().obtainStyledAttributes(
-                attrs, R.styleable.ImageButtonTitle);
-
-        mText = a.getString(R.styleable.ImageButtonTitle_android_text);
-    }
-
-    public String getText(){
-        return mText;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-        if (mText != null) {
-            gPaint.setARGB(255, 255, 255, 255);
-            gPaint.setTextSize(mTextSize);
-            float textWidth = gPaint.measureText(mText);
-            int x = (int) ((getWidth() - textWidth) / 2);
-            int y = getHeight() - mTextPadding;
-
-            canvas.drawText(mText, x, y, gPaint);
-        }
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
index 7b04133..f7dcad7 100644
--- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -26,18 +26,32 @@
 import android.graphics.PorterDuffXfermode;
 import android.os.AsyncTask;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
 import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.Editor;
+import com.android.gallery3d.filtershow.editors.EditorCurves;
+import com.android.gallery3d.filtershow.filters.FilterCurvesRepresentation;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
 import com.android.gallery3d.filtershow.filters.ImageFilterCurves;
-import com.android.gallery3d.filtershow.imageshow.ImageSlave;
+import com.android.gallery3d.filtershow.imageshow.ImageShow;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-public class ImageCurves extends ImageSlave {
+import java.util.HashMap;
+
+public class ImageCurves extends ImageShow {
 
     private static final String LOGTAG = "ImageCurves";
     Paint gPaint = new Paint();
     Path gPathSpline = new Path();
+    HashMap<Integer, String> mIdStrLut;
 
     private int mCurrentCurveIndex = Spline.RGB;
     private boolean mDidAddPoint = false;
@@ -51,17 +65,81 @@
     Path gHistoPath = new Path();
 
     boolean mDoingTouchMove = false;
+    private EditorCurves mEditorCurves;
+    private FilterCurvesRepresentation mFilterCurvesRepresentation;
 
     public ImageCurves(Context context) {
         super(context);
+        setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
         resetCurve();
     }
 
     public ImageCurves(Context context, AttributeSet attrs) {
         super(context, attrs);
+        setLayerType(LAYER_TYPE_SOFTWARE, gPaint);
         resetCurve();
     }
 
+    @Override
+    protected boolean enableComparison() {
+        return false;
+    }
+
+    @Override
+    public boolean useUtilityPanel() {
+        return true;
+    }
+
+    private void showPopupMenu(LinearLayout accessoryViewList) {
+        final Button button = (Button) accessoryViewList.findViewById(
+                R.id.applyEffect);
+        if (button == null) {
+            return;
+        }
+        if (mIdStrLut == null){
+            mIdStrLut = new HashMap<Integer, String>();
+            mIdStrLut.put(R.id.curve_menu_rgb,
+                    getContext().getString(R.string.curves_channel_rgb));
+            mIdStrLut.put(R.id.curve_menu_red,
+                    getContext().getString(R.string.curves_channel_red));
+            mIdStrLut.put(R.id.curve_menu_green,
+                    getContext().getString(R.string.curves_channel_green));
+            mIdStrLut.put(R.id.curve_menu_blue,
+                    getContext().getString(R.string.curves_channel_blue));
+        }
+        PopupMenu popupMenu = new PopupMenu(getActivity(), button);
+        popupMenu.getMenuInflater().inflate(R.menu.filtershow_menu_curves, popupMenu.getMenu());
+        popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+            @Override
+            public boolean onMenuItemClick(MenuItem item) {
+                setChannel(item.getItemId());
+                button.setText(mIdStrLut.get(item.getItemId()));
+                return true;
+            }
+        });
+        Editor.hackFixStrings(popupMenu.getMenu());
+        popupMenu.show();
+    }
+
+    @Override
+    public void openUtilityPanel(final LinearLayout accessoryViewList) {
+        Context context = accessoryViewList.getContext();
+        Button view = (Button) accessoryViewList.findViewById(R.id.applyEffect);
+        view.setText(context.getString(R.string.curves_channel_rgb));
+        view.setVisibility(View.VISIBLE);
+
+        view.setOnClickListener(new OnClickListener() {
+                @Override
+            public void onClick(View arg0) {
+                showPopupMenu(accessoryViewList);
+            }
+        });
+
+        if (view != null) {
+            view.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void nextChannel() {
         mCurrentCurveIndex = ((mCurrentCurveIndex + 1) % 4);
         invalidate();
@@ -73,15 +151,16 @@
     }
 
     private ImageFilterCurves curves() {
-        if (getMaster() != null) {
-            String filterName = getFilterName();
-            return (ImageFilterCurves) getImagePreset().getFilter(filterName);
+        String filterName = getFilterName();
+        ImagePreset p = getImagePreset();
+        if (p != null) {
+            return (ImageFilterCurves) FiltersManager.getManager().getFilter(ImageFilterCurves.class);
         }
         return null;
     }
 
     private Spline getSpline(int index) {
-        return curves().getSpline(index);
+        return mFilterCurvesRepresentation.getSpline(index);
     }
 
     @Override
@@ -93,8 +172,8 @@
     }
 
     public void resetCurve() {
-        if (getMaster() != null && curves() != null) {
-            curves().reset();
+        if (mFilterCurvesRepresentation != null) {
+            mFilterCurvesRepresentation.reset();
             updateCachedImage();
         }
     }
@@ -102,6 +181,9 @@
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
+        if (mFilterCurvesRepresentation == null) {
+            return;
+        }
 
         gPaint.setAntiAlias(true);
 
@@ -174,15 +256,30 @@
 
     @Override
     public synchronized boolean onTouchEvent(MotionEvent e) {
-        float posX = e.getX() / getWidth();
-        float posY = e.getY();
+        if (e.getPointerCount() != 1) {
+            return true;
+        }
+
+        if (didFinishScalingOperation()) {
+            return true;
+        }
+
         float margin = Spline.curveHandleSize() / 2;
+        float posX = e.getX();
+        if (posX < margin) {
+            posX = margin;
+        }
+        float posY = e.getY();
         if (posY < margin) {
             posY = margin;
         }
+        if (posX > getWidth() - margin) {
+            posX = getWidth() - margin;
+        }
         if (posY > getHeight() - margin) {
             posY = getHeight() - margin;
         }
+        posX = (posX - margin) / (getWidth() - 2 * margin);
         posY = (posY - margin) / (getHeight() - 2 * margin);
 
         if (e.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -196,7 +293,6 @@
             mDoingTouchMove = false;
             return true;
         }
-        mDoingTouchMove = true;
 
         if (mDidDelete) {
             return true;
@@ -206,35 +302,40 @@
             return true;
         }
 
-        Spline spline = getSpline(mCurrentCurveIndex);
-        int pick = mCurrentPick;
-        if (mCurrentControlPoint == null) {
-            pick = pickControlPoint(posX, posY);
-            if (pick == -1) {
-                mCurrentControlPoint = new ControlPoint(posX, posY);
-                pick = spline.addPoint(mCurrentControlPoint);
-                mDidAddPoint = true;
-            } else {
-                mCurrentControlPoint = spline.getPoint(pick);
+        if (e.getActionMasked() == MotionEvent.ACTION_MOVE) {
+            mDoingTouchMove = true;
+            Spline spline = getSpline(mCurrentCurveIndex);
+            int pick = mCurrentPick;
+            if (mCurrentControlPoint == null) {
+                pick = pickControlPoint(posX, posY);
+                if (pick == -1) {
+                    mCurrentControlPoint = new ControlPoint(posX, posY);
+                    pick = spline.addPoint(mCurrentControlPoint);
+                    mDidAddPoint = true;
+                } else {
+                    mCurrentControlPoint = spline.getPoint(pick);
+                }
+                mCurrentPick = pick;
             }
-            mCurrentPick = pick;
-        }
 
-        if (spline.isPointContained(posX, pick)) {
-            spline.movePoint(pick, posX, posY);
-        } else if (pick != -1 && spline.getNbPoints() > 2) {
-            spline.deletePoint(pick);
-            mDidDelete = true;
+            if (spline.isPointContained(posX, pick)) {
+                spline.movePoint(pick, posX, posY);
+            } else if (pick != -1 && spline.getNbPoints() > 2) {
+                spline.deletePoint(pick);
+                mDidDelete = true;
+            }
+            updateCachedImage();
+            invalidate();
         }
-        updateCachedImage();
-        invalidate();
         return true;
     }
 
     public synchronized void updateCachedImage() {
-        // update image
         if (getImagePreset() != null) {
             resetImageCaches(this);
+            if (mEditorCurves != null) {
+                mEditorCurves.commitLocalRepresentation();
+            }
             invalidate();
         }
     }
@@ -339,6 +440,15 @@
                 break;
             }
         }
+        mEditorCurves.commitLocalRepresentation();
         invalidate();
     }
+
+    public void setEditor(EditorCurves editorCurves) {
+        mEditorCurves = editorCurves;
+    }
+
+    public void setFilterDrawRepresentation(FilterCurvesRepresentation drawRep) {
+        mFilterCurvesRepresentation = drawRep;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java b/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java
new file mode 100644
index 0000000..ef40c5e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/ui/SelectionRenderer.java
@@ -0,0 +1,48 @@
+/*
+ * 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.gallery3d.filtershow.ui;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+
+public class SelectionRenderer {
+
+    public static void drawSelection(Canvas canvas, int left, int top, int right, int bottom,
+            int stroke, Paint paint) {
+        canvas.drawRect(left, top, right, top + stroke, paint);
+        canvas.drawRect(left, bottom - stroke, right, bottom, paint);
+        canvas.drawRect(left, top, left + stroke, bottom, paint);
+        canvas.drawRect(right - stroke, top, right, bottom, paint);
+    }
+
+    public static void drawSelection(Canvas canvas, int left, int top, int right, int bottom,
+            int stroke, Paint selectPaint, int border, Paint borderPaint) {
+        canvas.drawRect(left, top, right, top + stroke, selectPaint);
+        canvas.drawRect(left, bottom - stroke, right, bottom, selectPaint);
+        canvas.drawRect(left, top, left + stroke, bottom, selectPaint);
+        canvas.drawRect(right - stroke, top, right, bottom, selectPaint);
+        canvas.drawRect(left + stroke, top + stroke, right - stroke,
+                top + stroke + border, borderPaint);
+        canvas.drawRect(left + stroke, bottom - stroke - border, right - stroke,
+                bottom - stroke, borderPaint);
+        canvas.drawRect(left + stroke, top + stroke, left + stroke + border,
+                bottom - stroke, borderPaint);
+        canvas.drawRect(right - stroke - border, top + stroke, right - stroke,
+                bottom - stroke, borderPaint);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderController.java b/src/com/android/gallery3d/filtershow/ui/SliderController.java
deleted file mode 100644
index 7139ace..0000000
--- a/src/com/android/gallery3d/filtershow/ui/SliderController.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.ui;
-
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.view.MotionEvent;
-
-public class SliderController {
-    private static final String LOGTAG = "SliderController";
-
-    private float mCenterX;
-    private float mCenterY;
-    private float mCurrentX;
-    private float mCurrentY;
-    private int mValue = 100;
-    int mOriginalValue = 0;
-
-    private int mWidth = 0;
-    private int mHeight = 0;
-
-    private String mToast = null;
-
-    private final Paint mPaint = new Paint();
-
-    private SliderListener mListener = null;
-
-    private MODES mMode = MODES.NONE;
-    private static int mTextSize = 128;
-
-    private enum MODES {
-        NONE, DOWN, UP, MOVE
-    }
-
-    public void onDraw(Canvas canvas) {
-        if (mMode == MODES.NONE || mMode == MODES.UP) {
-            return;
-        }
-    }
-
-    public void drawToast(Canvas canvas) {
-        if (mToast != null) {
-            canvas.save();
-            mPaint.setTextSize(mTextSize);
-            float textWidth = mPaint.measureText(mToast);
-            int toastX = (int) ((getWidth() - textWidth) / 2.0f);
-            int toastY = (int) (getHeight() / 3.0f);
-
-            mPaint.setARGB(255, 0, 0, 0);
-            canvas.drawText(mToast, toastX - 2, toastY - 2, mPaint);
-            canvas.drawText(mToast, toastX - 2, toastY, mPaint);
-            canvas.drawText(mToast, toastX, toastY - 2, mPaint);
-            canvas.drawText(mToast, toastX + 2, toastY + 2, mPaint);
-            canvas.drawText(mToast, toastX + 2, toastY, mPaint);
-            canvas.drawText(mToast, toastX, toastY + 2, mPaint);
-            mPaint.setARGB(255, 255, 255, 255);
-            canvas.drawText(mToast, toastX, toastY, mPaint);
-            canvas.restore();
-        }
-    }
-
-    protected int computeValue() {
-        int delta = (int) (100 * (getCurrentX() - getCenterX()) / getWidth());
-        int value = mOriginalValue + delta;
-        if (value < -100) {
-            value = -100;
-        } else if (value > 100) {
-            value = 100;
-        }
-        setValue(value);
-        mToast = "" + value;
-        return value;
-    }
-
-    public void setValue(int value) {
-        mValue = value;
-    }
-
-    public int getWidth() {
-        return mWidth;
-    }
-
-    public int getHeight() {
-        return mHeight;
-    }
-
-    public void setWidth(int value) {
-        mWidth = value;
-    }
-
-    public void setHeight(int value) {
-        mHeight = value;
-    }
-
-    public float getCurrentX() {
-        return mCurrentX;
-    }
-
-    public float getCurrentY() {
-        return mCurrentY;
-    }
-
-    public float getCenterX() {
-        return mCenterX;
-    }
-
-    public float getCenterY() {
-        return mCenterY;
-    }
-
-    public void setActionDown(float x, float y) {
-        mCenterX = x;
-        mCenterY = y;
-        mCurrentX = x;
-        mCurrentY = y;
-        mMode = MODES.DOWN;
-        if (mListener != null) {
-            mListener.onTouchDown(x, y);
-        }
-    }
-
-    public void setActionMove(float x, float y) {
-        mCurrentX = x;
-        mCurrentY = y;
-        mMode = MODES.MOVE;
-        computeValue();
-        if (mListener != null) {
-            mListener.onNewValue(mValue);
-        }
-    }
-
-    public void setActionUp() {
-        mMode = MODES.UP;
-        mOriginalValue = computeValue();
-        if (mListener != null) {
-            mListener.onTouchUp();
-        }
-    }
-
-    public void setNoAction() {
-        mMode = MODES.NONE;
-    }
-
-    public void setListener(SliderListener listener) {
-        mListener = listener;
-    }
-
-    public boolean onTouchEvent(MotionEvent event) {
-        setNoAction();
-        switch (event.getActionMasked()) {
-            case (MotionEvent.ACTION_DOWN): {
-                setActionDown(event.getX(), event.getY());
-                break;
-            }
-            case (MotionEvent.ACTION_UP): {
-                setActionUp();
-                break;
-            }
-            case (MotionEvent.ACTION_CANCEL): {
-                setActionUp();
-                break;
-            }
-            case (MotionEvent.ACTION_MOVE): {
-                setActionMove(event.getX(), event.getY());
-                break;
-            }
-        }
-        return true;
-    }
-
-    public void reset() {
-        mOriginalValue = 0;
-    }
-
-}
diff --git a/src/com/android/gallery3d/filtershow/ui/SliderListener.java b/src/com/android/gallery3d/filtershow/ui/SliderListener.java
deleted file mode 100644
index 6d4718d..0000000
--- a/src/com/android/gallery3d/filtershow/ui/SliderListener.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.filtershow.ui;
-
-public interface SliderListener {
-    public void onNewValue(int value);
-    public void onTouchDown(float x, float y);
-    public void onTouchUp();
-}
diff --git a/src/com/android/gallery3d/filtershow/ui/Spline.java b/src/com/android/gallery3d/filtershow/ui/Spline.java
index 8334177..cadf2fd 100644
--- a/src/com/android/gallery3d/filtershow/ui/Spline.java
+++ b/src/com/android/gallery3d/filtershow/ui/Spline.java
@@ -98,7 +98,7 @@
     }
 
     public boolean isOriginal() {
-        if (this.getNbPoints() > 2) {
+        if (this.getNbPoints() != 2) {
             return false;
         }
         if (mPoints.elementAt(0).x != 0 || mPoints.elementAt(0).y != 1) {
@@ -110,6 +110,12 @@
         return true;
     }
 
+    public void reset() {
+        mPoints.clear();
+        addPoint(0.0f, 1.0f);
+        addPoint(1.0f, 0.0f);
+    }
+
     private void drawHandles(Canvas canvas, Drawable indicator, float centerX, float centerY) {
         int left = (int) centerX - mCurveHandleSize / 2;
         int top = (int) centerY - mCurveHandleSize / 2;
@@ -372,6 +378,9 @@
 
     public void deletePoint(int n) {
         mPoints.remove(n);
+        if (mPoints.size() < 2) {
+            reset();
+        }
         Collections.sort(mPoints);
     }
 
diff --git a/src/com/android/gallery3d/gadget/MediaSetSource.java b/src/com/android/gallery3d/gadget/MediaSetSource.java
index caeff2a..458651c 100644
--- a/src/com/android/gallery3d/gadget/MediaSetSource.java
+++ b/src/com/android/gallery3d/gadget/MediaSetSource.java
@@ -22,93 +22,212 @@
 
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 
 public class MediaSetSource implements WidgetSource, ContentListener {
-    private static final int CACHE_SIZE = 32;
-
-    @SuppressWarnings("unused")
     private static final String TAG = "MediaSetSource";
 
-    private MediaSet mSource;
-    private MediaItem mCache[] = new MediaItem[CACHE_SIZE];
-    private int mCacheStart;
-    private int mCacheEnd;
-    private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
+    private DataManager mDataManager;
+    private Path mAlbumPath;
 
-    private ContentListener mContentListener;
+    private WidgetSource mSource;
 
-    public MediaSetSource(MediaSet source) {
-        mSource = Utils.checkNotNull(source);
-        mSource.addContentListener(this);
-    }
+    private MediaSet mRootSet;
+    private ContentListener mListener;
 
-    @Override
-    public void close() {
-        mSource.removeContentListener(this);
-    }
-
-    private void ensureCacheRange(int index) {
-        if (index >= mCacheStart && index < mCacheEnd) return;
-
-        long token = Binder.clearCallingIdentity();
-        try {
-            mCacheStart = index;
-            ArrayList<MediaItem> items = mSource.getMediaItem(mCacheStart, CACHE_SIZE);
-            mCacheEnd = mCacheStart + items.size();
-            items.toArray(mCache);
-        } finally {
-            Binder.restoreCallingIdentity(token);
+    public MediaSetSource(DataManager manager, String albumPath) {
+        MediaSet mediaSet = (MediaSet) manager.getMediaObject(albumPath);
+        if (mediaSet != null) {
+            mSource = new CheckedMediaSetSource(mediaSet);
+            return;
         }
-    }
 
-    @Override
-    public synchronized Uri getContentUri(int index) {
-        ensureCacheRange(index);
-        if (index < mCacheStart || index >= mCacheEnd) return null;
-        return mCache[index - mCacheStart].getContentUri();
-    }
-
-    @Override
-    public synchronized Bitmap getImage(int index) {
-        ensureCacheRange(index);
-        if (index < mCacheStart || index >= mCacheEnd) return null;
-        return WidgetUtils.createWidgetBitmap(mCache[index - mCacheStart]);
-    }
-
-    @Override
-    public void reload() {
-        long version = mSource.reload();
-        if (mSourceVersion != version) {
-            mSourceVersion = version;
-            mCacheStart = 0;
-            mCacheEnd = 0;
-            Arrays.fill(mCache, null);
-        }
-    }
-
-    @Override
-    public void setContentListener(ContentListener listener) {
-        mContentListener = listener;
+        // Initialize source to an empty source until the album path can be resolved
+        mDataManager = Utils.checkNotNull(manager);
+        mAlbumPath = Path.fromString(albumPath);
+        mSource = new EmptySource();
+        monitorRootPath();
     }
 
     @Override
     public int size() {
-        long token = Binder.clearCallingIdentity();
-        try {
-            return mSource.getMediaItemCount();
-        } finally {
-            Binder.restoreCallingIdentity(token);
+        return mSource.size();
+    }
+
+    @Override
+    public Bitmap getImage(int index) {
+        return mSource.getImage(index);
+    }
+
+    @Override
+    public Uri getContentUri(int index) {
+        return mSource.getContentUri(index);
+    }
+
+    @Override
+    public synchronized void setContentListener(ContentListener listener) {
+        if (mRootSet != null) {
+            mListener = listener;
+        } else {
+            mSource.setContentListener(listener);
         }
     }
 
     @Override
+    public void reload() {
+        mSource.reload();
+    }
+
+    @Override
+    public void close() {
+        mSource.close();
+    }
+
+    @Override
     public void onContentDirty() {
-        if (mContentListener != null) mContentListener.onContentDirty();
+        resolveAlbumPath();
+    }
+
+    private void monitorRootPath() {
+        String rootPath = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
+        mRootSet = (MediaSet) mDataManager.getMediaObject(rootPath);
+        mRootSet.addContentListener(this);
+    }
+
+    private synchronized void resolveAlbumPath() {
+        if (mDataManager == null) return;
+        MediaSet mediaSet = (MediaSet) mDataManager.getMediaObject(mAlbumPath);
+        if (mediaSet != null) {
+            // Clear the reference instead of removing the listener
+            // to get around a concurrent modification exception.
+            mRootSet = null;
+
+            mSource = new CheckedMediaSetSource(mediaSet);
+            if (mListener != null) {
+                mListener.onContentDirty();
+                mSource.setContentListener(mListener);
+                mListener = null;
+            }
+            mDataManager = null;
+            mAlbumPath = null;
+        }
+    }
+
+    private static class CheckedMediaSetSource implements WidgetSource, ContentListener {
+        private static final int CACHE_SIZE = 32;
+
+        @SuppressWarnings("unused")
+        private static final String TAG = "CheckedMediaSetSource";
+
+        private MediaSet mSource;
+        private MediaItem mCache[] = new MediaItem[CACHE_SIZE];
+        private int mCacheStart;
+        private int mCacheEnd;
+        private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
+
+        private ContentListener mContentListener;
+
+        public CheckedMediaSetSource(MediaSet source) {
+            mSource = Utils.checkNotNull(source);
+            mSource.addContentListener(this);
+        }
+
+        @Override
+        public void close() {
+            mSource.removeContentListener(this);
+        }
+
+        private void ensureCacheRange(int index) {
+            if (index >= mCacheStart && index < mCacheEnd) return;
+
+            long token = Binder.clearCallingIdentity();
+            try {
+                mCacheStart = index;
+                ArrayList<MediaItem> items = mSource.getMediaItem(mCacheStart, CACHE_SIZE);
+                mCacheEnd = mCacheStart + items.size();
+                items.toArray(mCache);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public synchronized Uri getContentUri(int index) {
+            ensureCacheRange(index);
+            if (index < mCacheStart || index >= mCacheEnd) return null;
+            return mCache[index - mCacheStart].getContentUri();
+        }
+
+        @Override
+        public synchronized Bitmap getImage(int index) {
+            ensureCacheRange(index);
+            if (index < mCacheStart || index >= mCacheEnd) return null;
+            return WidgetUtils.createWidgetBitmap(mCache[index - mCacheStart]);
+        }
+
+        @Override
+        public void reload() {
+            long version = mSource.reload();
+            if (mSourceVersion != version) {
+                mSourceVersion = version;
+                mCacheStart = 0;
+                mCacheEnd = 0;
+                Arrays.fill(mCache, null);
+            }
+        }
+
+        @Override
+        public void setContentListener(ContentListener listener) {
+            mContentListener = listener;
+        }
+
+        @Override
+        public int size() {
+            long token = Binder.clearCallingIdentity();
+            try {
+                return mSource.getMediaItemCount();
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void onContentDirty() {
+            if (mContentListener != null) mContentListener.onContentDirty();
+        }
+    }
+
+    private static class EmptySource implements WidgetSource {
+
+        @Override
+        public int size() {
+            return 0;
+        }
+
+        @Override
+        public Bitmap getImage(int index) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public Uri getContentUri(int index) {
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
+        public void setContentListener(ContentListener listener) {}
+
+        @Override
+        public void reload() {}
+
+        @Override
+        public void close() {}
     }
 }
diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java
index 331e7d2..eb81b6e 100644
--- a/src/com/android/gallery3d/gadget/WidgetConfigure.java
+++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java
@@ -23,13 +23,20 @@
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Bundle;
+import android.util.Log;
 import android.widget.RemoteViews;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.AlbumPicker;
-import com.android.gallery3d.app.CropImage;
 import com.android.gallery3d.app.DialogPicker;
+import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.LocalAlbum;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropExtras;
 
 public class WidgetConfigure extends Activity {
     @SuppressWarnings("unused")
@@ -142,14 +149,14 @@
         int widgetHeight = Math.round(height * scale);
 
         mPickedItem = data.getData();
-        Intent request = new Intent(CropImage.ACTION_CROP, mPickedItem)
-                .putExtra(CropImage.KEY_OUTPUT_X, widgetWidth)
-                .putExtra(CropImage.KEY_OUTPUT_Y, widgetHeight)
-                .putExtra(CropImage.KEY_ASPECT_X, widgetWidth)
-                .putExtra(CropImage.KEY_ASPECT_Y, widgetHeight)
-                .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true)
-                .putExtra(CropImage.KEY_SCALE, true)
-                .putExtra(CropImage.KEY_RETURN_DATA, true);
+        Intent request = new Intent(FilterShowActivity.CROP_ACTION, mPickedItem)
+                .putExtra(CropExtras.KEY_OUTPUT_X, widgetWidth)
+                .putExtra(CropExtras.KEY_OUTPUT_Y, widgetHeight)
+                .putExtra(CropExtras.KEY_ASPECT_X, widgetWidth)
+                .putExtra(CropExtras.KEY_ASPECT_Y, widgetHeight)
+                .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true)
+                .putExtra(CropExtras.KEY_SCALE, true)
+                .putExtra(CropExtras.KEY_RETURN_DATA, true);
         startActivityForResult(request, REQUEST_CROP_IMAGE);
     }
 
@@ -157,8 +164,21 @@
         String albumPath = data.getStringExtra(AlbumPicker.KEY_ALBUM_PATH);
         WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this);
         try {
+            String relativePath = null;
+            GalleryApp galleryApp = (GalleryApp) getApplicationContext();
+            DataManager manager = galleryApp.getDataManager();
+            Path path = Path.fromString(albumPath);
+            MediaSet mediaSet = (MediaSet) manager.getMediaObject(path);
+            if (mediaSet instanceof LocalAlbum) {
+                int bucketId = Integer.parseInt(path.getSuffix());
+                // If the chosen album is a local album, find relative path
+                // Otherwise, leave the relative path field empty
+                relativePath = LocalAlbum.getRelativePath(bucketId);
+                Log.i(TAG, "Setting widget, album path: " + albumPath
+                        + ", relative path: " + relativePath);
+            }
             helper.setWidget(mAppWidgetId,
-                    WidgetDatabaseHelper.TYPE_ALBUM, albumPath);
+                    WidgetDatabaseHelper.TYPE_ALBUM, albumPath, relativePath);
             updateWidgetAndFinish(helper.getEntry(mAppWidgetId));
         } finally {
             helper.close();
@@ -173,7 +193,7 @@
         } else if (widgetType == R.id.widget_type_shuffle) {
             WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this);
             try {
-                helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null);
+                helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null, null);
                 updateWidgetAndFinish(helper.getEntry(mAppWidgetId));
             } finally {
                 helper.close();
diff --git a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
index c411c36..c014584 100644
--- a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
+++ b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java
@@ -36,7 +36,9 @@
     private static final String TAG = "PhotoDatabaseHelper";
     private static final String DATABASE_NAME = "launcher.db";
 
-    private static final int DATABASE_VERSION = 4;
+    // Increment the database version to 5. In version 5, we
+    // add a column in widgets table to record relative paths.
+    private static final int DATABASE_VERSION = 5;
 
     private static final String TABLE_WIDGETS = "widgets";
 
@@ -45,6 +47,7 @@
     private static final String FIELD_PHOTO_BLOB = "photoBlob";
     private static final String FIELD_WIDGET_TYPE = "widgetType";
     private static final String FIELD_ALBUM_PATH = "albumPath";
+    private static final String FIELD_RELATIVE_PATH = "relativePath";
 
     public static final int TYPE_SINGLE_PHOTO = 0;
     public static final int TYPE_SHUFFLE = 1;
@@ -52,12 +55,13 @@
 
     private static final String[] PROJECTION = {
             FIELD_WIDGET_TYPE, FIELD_IMAGE_URI, FIELD_PHOTO_BLOB, FIELD_ALBUM_PATH,
-            FIELD_APPWIDGET_ID};
+            FIELD_APPWIDGET_ID, FIELD_RELATIVE_PATH};
     private static final int INDEX_WIDGET_TYPE = 0;
     private static final int INDEX_IMAGE_URI = 1;
     private static final int INDEX_PHOTO_BLOB = 2;
     private static final int INDEX_ALBUM_PATH = 3;
     private static final int INDEX_APPWIDGET_ID = 4;
+    private static final int INDEX_RELATIVE_PATH = 5;
     private static final String WHERE_APPWIDGET_ID = FIELD_APPWIDGET_ID + " = ?";
     private static final String WHERE_WIDGET_TYPE = FIELD_WIDGET_TYPE + " = ?";
 
@@ -67,6 +71,7 @@
         public String imageUri;
         public byte imageData[];
         public String albumPath;
+        public String relativePath;
 
         private Entry() {}
 
@@ -78,6 +83,7 @@
                 imageData = cursor.getBlob(INDEX_PHOTO_BLOB);
             } else if (type == TYPE_ALBUM) {
                 albumPath = cursor.getString(INDEX_ALBUM_PATH);
+                relativePath = cursor.getString(INDEX_RELATIVE_PATH);
             }
         }
 
@@ -97,7 +103,8 @@
                 + FIELD_WIDGET_TYPE + " INTEGER DEFAULT 0, "
                 + FIELD_IMAGE_URI + " TEXT, "
                 + FIELD_ALBUM_PATH + " TEXT, "
-                + FIELD_PHOTO_BLOB + " BLOB)");
+                + FIELD_PHOTO_BLOB + " BLOB, "
+                + FIELD_RELATIVE_PATH + " TEXT)");
     }
 
     private void saveData(SQLiteDatabase db, int oldVersion, ArrayList<Entry> data) {
@@ -157,20 +164,27 @@
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        int version = oldVersion;
-
-        if (version != DATABASE_VERSION) {
+        if (oldVersion < 4) {
+            // Table "photos" is renamed to "widget" in version 4
             ArrayList<Entry> data = new ArrayList<Entry>();
             saveData(db, oldVersion, data);
 
             Log.w(TAG, "destroying all old data.");
-            // Table "photos" is renamed to "widget" in version 4
             db.execSQL("DROP TABLE IF EXISTS photos");
             db.execSQL("DROP TABLE IF EXISTS " + TABLE_WIDGETS);
             onCreate(db);
 
             restoreData(db, data);
         }
+        // Add a column for relative path
+        if (oldVersion < DATABASE_VERSION) {
+            try {
+                db.execSQL("ALTER TABLE widgets ADD COLUMN relativePath TEXT");
+            } catch (Throwable t) {
+                Log.e(TAG, "Failed to add the column for relative path.");
+                return;
+            }
+        }
     }
 
     /**
@@ -201,12 +215,13 @@
         }
     }
 
-    public boolean setWidget(int id, int type, String albumPath) {
+    public boolean setWidget(int id, int type, String albumPath, String relativePath) {
         try {
             ContentValues values = new ContentValues();
             values.put(FIELD_APPWIDGET_ID, id);
             values.put(FIELD_WIDGET_TYPE, type);
             values.put(FIELD_ALBUM_PATH, Utils.ensureNotNull(albumPath));
+            values.put(FIELD_RELATIVE_PATH, relativePath);
             getWritableDatabase().replaceOrThrow(TABLE_WIDGETS, null, values);
             return true;
         } catch (Throwable e) {
@@ -223,7 +238,8 @@
                     WHERE_APPWIDGET_ID, new String[] {String.valueOf(appWidgetId)},
                     null, null, null);
             if (cursor == null || !cursor.moveToNext()) {
-                Log.e(TAG, "query fail: empty cursor: " + cursor);
+                Log.e(TAG, "query fail: empty cursor: " + cursor + " appWidgetId: "
+                        + appWidgetId);
                 return null;
             }
             return new Entry(appWidgetId, cursor);
@@ -271,6 +287,7 @@
             values.put(FIELD_ALBUM_PATH, entry.albumPath);
             values.put(FIELD_IMAGE_URI, entry.imageUri);
             values.put(FIELD_PHOTO_BLOB, entry.imageData);
+            values.put(FIELD_RELATIVE_PATH, entry.relativePath);
             getWritableDatabase().insert(TABLE_WIDGETS, null, values);
         } catch (Throwable e) {
             Log.e(TAG, "set widget fail", e);
diff --git a/src/com/android/gallery3d/gadget/WidgetService.java b/src/com/android/gallery3d/gadget/WidgetService.java
index 334b15a..94dd164 100644
--- a/src/com/android/gallery3d/gadget/WidgetService.java
+++ b/src/com/android/gallery3d/gadget/WidgetService.java
@@ -28,9 +28,6 @@
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.data.ContentListener;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.MediaSet;
-import com.android.gallery3d.data.Path;
 
 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
 public class WidgetService extends RemoteViewsService {
@@ -52,33 +49,6 @@
                 id, type, albumPath);
     }
 
-    private static class EmptySource implements WidgetSource {
-
-        @Override
-        public int size() {
-            return 0;
-        }
-
-        @Override
-        public Bitmap getImage(int index) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public Uri getContentUri(int index) {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void setContentListener(ContentListener listener) {}
-
-        @Override
-        public void reload() {}
-
-        @Override
-        public void close() {}
-    }
-
     private static class PhotoRVFactory implements
             RemoteViewsService.RemoteViewsFactory, ContentListener {
 
@@ -99,12 +69,7 @@
         @Override
         public void onCreate() {
             if (mType == WidgetDatabaseHelper.TYPE_ALBUM) {
-                Path path = Path.fromString(mAlbumPath);
-                DataManager manager = mApp.getDataManager();
-                MediaSet mediaSet = (MediaSet) manager.getMediaObject(path);
-                mSource = mediaSet == null
-                        ? new EmptySource()
-                        : new MediaSetSource(mediaSet);
+                mSource = new MediaSetSource(mApp.getDataManager(), mAlbumPath);
             } else {
                 mSource = new LocalPhotoSource(mApp.getAndroidContext());
             }
diff --git a/src/com/android/gallery3d/glrenderer/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java
new file mode 100644
index 0000000..2e77b90
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+
+import java.util.WeakHashMap;
+
+// BasicTexture is a Texture corresponds to a real GL texture.
+// The state of a BasicTexture indicates whether its data is loaded to GL memory.
+// If a BasicTexture is loaded into GL memory, it has a GL texture id.
+public abstract class BasicTexture implements Texture {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "BasicTexture";
+    protected static final int UNSPECIFIED = -1;
+
+    protected static final int STATE_UNLOADED = 0;
+    protected static final int STATE_LOADED = 1;
+    protected static final int STATE_ERROR = -1;
+
+    // Log a warning if a texture is larger along a dimension
+    private static final int MAX_TEXTURE_SIZE = 4096;
+
+    protected int mId = -1;
+    protected int mState;
+
+    protected int mWidth = UNSPECIFIED;
+    protected int mHeight = UNSPECIFIED;
+
+    protected int mTextureWidth;
+    protected int mTextureHeight;
+
+    private boolean mHasBorder;
+
+    protected GLCanvas mCanvasRef = null;
+    private static WeakHashMap<BasicTexture, Object> sAllTextures
+            = new WeakHashMap<BasicTexture, Object>();
+    private static ThreadLocal sInFinalizer = new ThreadLocal();
+
+    protected BasicTexture(GLCanvas canvas, int id, int state) {
+        setAssociatedCanvas(canvas);
+        mId = id;
+        mState = state;
+        synchronized (sAllTextures) {
+            sAllTextures.put(this, null);
+        }
+    }
+
+    protected BasicTexture() {
+        this(null, 0, STATE_UNLOADED);
+    }
+
+    protected void setAssociatedCanvas(GLCanvas canvas) {
+        mCanvasRef = canvas;
+    }
+
+    /**
+     * Sets the content size of this texture. In OpenGL, the actual texture
+     * size must be of power of 2, the size of the content may be smaller.
+     */
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        mTextureWidth = width > 0 ? Utils.nextPowerOf2(width) : 0;
+        mTextureHeight = height > 0 ? Utils.nextPowerOf2(height) : 0;
+        if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
+            Log.w(TAG, String.format("texture is too large: %d x %d",
+                    mTextureWidth, mTextureHeight), new Exception());
+        }
+    }
+
+    public boolean isFlippedVertically() {
+      return false;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    // Returns the width rounded to the next power of 2.
+    public int getTextureWidth() {
+        return mTextureWidth;
+    }
+
+    // Returns the height rounded to the next power of 2.
+    public int getTextureHeight() {
+        return mTextureHeight;
+    }
+
+    // Returns true if the texture has one pixel transparent border around the
+    // actual content. This is used to avoid jigged edges.
+    //
+    // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
+    // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
+    // covered by the texture will use the color of the edge texel. If we add
+    // the transparent border, the color of the edge texel will be mixed with
+    // appropriate amount of transparent.
+    //
+    // Currently our background is black, so we can draw the thumbnails without
+    // enabling blending.
+    public boolean hasBorder() {
+        return mHasBorder;
+    }
+
+    protected void setBorder(boolean hasBorder) {
+        mHasBorder = hasBorder;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y) {
+        canvas.drawTexture(this, x, y, getWidth(), getHeight());
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        canvas.drawTexture(this, x, y, w, h);
+    }
+
+    // onBind is called before GLCanvas binds this texture.
+    // It should make sure the data is uploaded to GL memory.
+    abstract protected boolean onBind(GLCanvas canvas);
+
+    // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
+    abstract protected int getTarget();
+
+    public boolean isLoaded() {
+        return mState == STATE_LOADED;
+    }
+
+    // recycle() is called when the texture will never be used again,
+    // so it can free all resources.
+    public void recycle() {
+        freeResource();
+    }
+
+    // yield() is called when the texture will not be used temporarily,
+    // so it can free some resources.
+    // The default implementation unloads the texture from GL memory, so
+    // the subclass should make sure it can reload the texture to GL memory
+    // later, or it will have to override this method.
+    public void yield() {
+        freeResource();
+    }
+
+    private void freeResource() {
+        GLCanvas canvas = mCanvasRef;
+        if (canvas != null && mId != -1) {
+            canvas.unloadTexture(this);
+            mId = -1; // Don't free it again.
+        }
+        mState = STATE_UNLOADED;
+        setAssociatedCanvas(null);
+    }
+
+    @Override
+    protected void finalize() {
+        sInFinalizer.set(BasicTexture.class);
+        recycle();
+        sInFinalizer.set(null);
+    }
+
+    // This is for deciding if we can call Bitmap's recycle().
+    // We cannot call Bitmap's recycle() in finalizer because at that point
+    // the finalizer of Bitmap may already be called so recycle() will crash.
+    public static boolean inFinalizer() {
+        return sInFinalizer.get() != null;
+    }
+
+    public static void yieldAllTextures() {
+        synchronized (sAllTextures) {
+            for (BasicTexture t : sAllTextures.keySet()) {
+                t.yield();
+            }
+        }
+    }
+
+    public static void invalidateAllTextures() {
+        synchronized (sAllTextures) {
+            for (BasicTexture t : sAllTextures.keySet()) {
+                t.mState = STATE_UNLOADED;
+                t.setAssociatedCanvas(null);
+            }
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
new file mode 100644
index 0000000..100b0b3
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+
+import junit.framework.Assert;
+
+// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
+//
+// The texture does not own the Bitmap. The user should make sure the Bitmap
+// is valid during the texture's lifetime. When the texture is recycled, it
+// does not free the Bitmap.
+public class BitmapTexture extends UploadedTexture {
+    protected Bitmap mContentBitmap;
+
+    public BitmapTexture(Bitmap bitmap) {
+        this(bitmap, false);
+    }
+
+    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
+        super(hasBorder);
+        Assert.assertTrue(bitmap != null && !bitmap.isRecycled());
+        mContentBitmap = bitmap;
+    }
+
+    @Override
+    protected void onFreeBitmap(Bitmap bitmap) {
+        // Do nothing.
+    }
+
+    @Override
+    protected Bitmap onGetBitmap() {
+        return mContentBitmap;
+    }
+
+    public Bitmap getBitmap() {
+        return mContentBitmap;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/CanvasTexture.java b/src/com/android/gallery3d/glrenderer/CanvasTexture.java
new file mode 100644
index 0000000..bff9d4b
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/CanvasTexture.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+
+// CanvasTexture is a texture whose content is the drawing on a Canvas.
+// The subclasses should override onDraw() to draw on the bitmap.
+// By default CanvasTexture is not opaque.
+abstract class CanvasTexture extends UploadedTexture {
+    protected Canvas mCanvas;
+    private final Config mConfig;
+
+    public CanvasTexture(int width, int height) {
+        mConfig = Config.ARGB_8888;
+        setSize(width, height);
+        setOpaque(false);
+    }
+
+    @Override
+    protected Bitmap onGetBitmap() {
+        Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
+        mCanvas = new Canvas(bitmap);
+        onDraw(mCanvas, bitmap);
+        return bitmap;
+    }
+
+    @Override
+    protected void onFreeBitmap(Bitmap bitmap) {
+        if (!inFinalizer()) {
+            bitmap.recycle();
+        }
+    }
+
+    abstract protected void onDraw(Canvas canvas, Bitmap backing);
+}
diff --git a/src/com/android/gallery3d/glrenderer/ColorTexture.java b/src/com/android/gallery3d/glrenderer/ColorTexture.java
new file mode 100644
index 0000000..904c78e
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ColorTexture.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import com.android.gallery3d.common.Utils;
+
+// ColorTexture is a texture which fills the rectangle with the specified color.
+public class ColorTexture implements Texture {
+
+    private final int mColor;
+    private int mWidth;
+    private int mHeight;
+
+    public ColorTexture(int color) {
+        mColor = color;
+        mWidth = 1;
+        mHeight = 1;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y) {
+        draw(canvas, x, y, mWidth, mHeight);
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        canvas.fillRect(x, y, w, h, mColor);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return Utils.isOpaque(mColor);
+    }
+
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/ExtTexture.java b/src/com/android/gallery3d/glrenderer/ExtTexture.java
new file mode 100644
index 0000000..af76300
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ExtTexture.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+// ExtTexture is a texture whose content comes from a external texture.
+// Before drawing, setSize() should be called.
+public class ExtTexture extends BasicTexture {
+
+    private int mTarget;
+
+    public ExtTexture(GLCanvas canvas, int target) {
+        GLId glId = canvas.getGLId();
+        mId = glId.generateTexture();
+        mTarget = target;
+    }
+
+    private void uploadToCanvas(GLCanvas canvas) {
+        canvas.setTextureParameters(this);
+        setAssociatedCanvas(canvas);
+        mState = STATE_LOADED;
+    }
+
+    @Override
+    protected boolean onBind(GLCanvas canvas) {
+        if (!isLoaded()) {
+            uploadToCanvas(canvas);
+        }
+
+        return true;
+    }
+
+    @Override
+    public int getTarget() {
+        return mTarget;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return true;
+    }
+
+    @Override
+    public void yield() {
+        // we cannot free the texture because we have no backup.
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeInTexture.java b/src/com/android/gallery3d/glrenderer/FadeInTexture.java
new file mode 100644
index 0000000..838d465
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeInTexture.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.glrenderer;
+
+
+// FadeInTexture is a texture which begins with a color, then gradually animates
+// into a given texture.
+public class FadeInTexture extends FadeTexture implements Texture {
+    @SuppressWarnings("unused")
+    private static final String TAG = "FadeInTexture";
+
+    private final int mColor;
+    private final TiledTexture mTexture;
+
+    public FadeInTexture(int color, TiledTexture texture) {
+        super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
+        mColor = color;
+        mTexture = texture;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        if (isAnimating()) {
+            mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
+        } else {
+            mTexture.draw(canvas, x, y, w, h);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeOutTexture.java b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java
new file mode 100644
index 0000000..b05f3b6
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.glrenderer;
+
+
+// FadeOutTexture is a texture which begins with a given texture, then gradually animates
+// into fading out totally.
+public class FadeOutTexture extends FadeTexture {
+    @SuppressWarnings("unused")
+    private static final String TAG = "FadeOutTexture";
+
+    private final BasicTexture mTexture;
+
+    public FadeOutTexture(BasicTexture texture) {
+        super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
+        mTexture = texture;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        if (isAnimating()) {
+            canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
+            canvas.setAlpha(getRatio());
+            mTexture.draw(canvas, x, y, w, h);
+            canvas.restore();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/FadeTexture.java b/src/com/android/gallery3d/glrenderer/FadeTexture.java
new file mode 100644
index 0000000..002c90f
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/FadeTexture.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.glrenderer;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.ui.AnimationTime;
+
+// FadeTexture is a texture which fades the given texture along the time.
+public abstract class FadeTexture implements Texture {
+    @SuppressWarnings("unused")
+    private static final String TAG = "FadeTexture";
+
+    // The duration of the fading animation in milliseconds
+    public static final int DURATION = 180;
+
+    private final long mStartTime;
+    private final int mWidth;
+    private final int mHeight;
+    private final boolean mIsOpaque;
+    private boolean mIsAnimating;
+
+    public FadeTexture(int width, int height, boolean opaque) {
+        mWidth = width;
+        mHeight = height;
+        mIsOpaque = opaque;
+        mStartTime = now();
+        mIsAnimating = true;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y) {
+        draw(canvas, x, y, mWidth, mHeight);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mIsOpaque;
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    public boolean isAnimating() {
+        if (mIsAnimating) {
+            if (now() - mStartTime >= DURATION) {
+                mIsAnimating = false;
+            }
+        }
+        return mIsAnimating;
+    }
+
+    protected float getRatio() {
+        float r = (float)(now() - mStartTime) / DURATION;
+        return Utils.clamp(1.0f - r, 0.0f, 1.0f);
+    }
+
+    private long now() {
+        return AnimationTime.get();
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java
new file mode 100644
index 0000000..305e905
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import javax.microedition.khronos.opengles.GL11;
+
+//
+// GLCanvas gives a convenient interface to draw using OpenGL.
+//
+// When a rectangle is specified in this interface, it means the region
+// [x, x+width) * [y, y+height)
+//
+public interface GLCanvas {
+
+    public GLId getGLId();
+
+    // Tells GLCanvas the size of the underlying GL surface. This should be
+    // called before first drawing and when the size of GL surface is changed.
+    // This is called by GLRoot and should not be called by the clients
+    // who only want to draw on the GLCanvas. Both width and height must be
+    // nonnegative.
+    public abstract void setSize(int width, int height);
+
+    // Clear the drawing buffers. This should only be used by GLRoot.
+    public abstract void clearBuffer();
+
+    public abstract void clearBuffer(float[] argb);
+
+    // Sets and gets the current alpha, alpha must be in [0, 1].
+    public abstract void setAlpha(float alpha);
+
+    public abstract float getAlpha();
+
+    // (current alpha) = (current alpha) * alpha
+    public abstract void multiplyAlpha(float alpha);
+
+    // Change the current transform matrix.
+    public abstract void translate(float x, float y, float z);
+
+    public abstract void translate(float x, float y);
+
+    public abstract void scale(float sx, float sy, float sz);
+
+    public abstract void rotate(float angle, float x, float y, float z);
+
+    public abstract void multiplyMatrix(float[] mMatrix, int offset);
+
+    // Pushes the configuration state (matrix, and alpha) onto
+    // a private stack.
+    public abstract void save();
+
+    // Same as save(), but only save those specified in saveFlags.
+    public abstract void save(int saveFlags);
+
+    public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
+    public static final int SAVE_FLAG_ALPHA = 0x01;
+    public static final int SAVE_FLAG_MATRIX = 0x02;
+
+    // Pops from the top of the stack as current configuration state (matrix,
+    // alpha, and clip). This call balances a previous call to save(), and is
+    // used to remove all modifications to the configuration state since the
+    // last save call.
+    public abstract void restore();
+
+    // Draws a line using the specified paint from (x1, y1) to (x2, y2).
+    // (Both end points are included).
+    public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
+
+    // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
+    // (Both end points are included).
+    public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
+
+    // Fills the specified rectangle with the specified color.
+    public abstract void fillRect(float x, float y, float width, float height, int color);
+
+    // Draws a texture to the specified rectangle.
+    public abstract void drawTexture(
+            BasicTexture texture, int x, int y, int width, int height);
+
+    public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+            int uvBuffer, int indexBuffer, int indexCount);
+
+    // Draws the source rectangle part of the texture to the target rectangle.
+    public abstract void drawTexture(BasicTexture texture, RectF source, RectF target);
+
+    // Draw a texture with a specified texture transform.
+    public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform,
+                int x, int y, int w, int h);
+
+    // Draw two textures to the specified rectangle. The actual texture used is
+    // from * (1 - ratio) + to * ratio
+    // The two textures must have the same size.
+    public abstract void drawMixed(BasicTexture from, int toColor,
+            float ratio, int x, int y, int w, int h);
+
+    // Draw a region of a texture and a specified color to the specified
+    // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
+    // The region of the texture is defined by parameter "src". The target
+    // rectangle is specified by parameter "target".
+    public abstract void drawMixed(BasicTexture from, int toColor,
+            float ratio, RectF src, RectF target);
+
+    // Unloads the specified texture from the canvas. The resource allocated
+    // to draw the texture will be released. The specified texture will return
+    // to the unloaded state. This function should be called only from
+    // BasicTexture or its descendant
+    public abstract boolean unloadTexture(BasicTexture texture);
+
+    // Delete the specified buffer object, similar to unloadTexture.
+    public abstract void deleteBuffer(int bufferId);
+
+    // Delete the textures and buffers in GL side. This function should only be
+    // called in the GL thread.
+    public abstract void deleteRecycledResources();
+
+    // Dump statistics information and clear the counters. For debug only.
+    public abstract void dumpStatisticsAndClear();
+
+    public abstract void beginRenderTarget(RawTexture texture);
+
+    public abstract void endRenderTarget();
+
+    /**
+     * Sets texture parameters to use GL_CLAMP_TO_EDGE for both
+     * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be
+     * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER.
+     * bindTexture() must be called prior to this.
+     *
+     * @param texture The texture to set parameters on.
+     */
+    public abstract void setTextureParameters(BasicTexture texture);
+
+    /**
+     * Initializes the texture to a size by calling texImage2D on it.
+     *
+     * @param texture The texture to initialize the size.
+     * @param format The texture format (e.g. GL_RGBA)
+     * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+     */
+    public abstract void initializeTextureSize(BasicTexture texture, int format, int type);
+
+    /**
+     * Initializes the texture to a size by calling texImage2D on it.
+     *
+     * @param texture The texture to initialize the size.
+     * @param bitmap The bitmap to initialize the bitmap with.
+     */
+    public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap);
+
+    /**
+     * Calls glTexSubImage2D to upload a bitmap to the texture.
+     *
+     * @param texture The target texture to write to.
+     * @param xOffset Specifies a texel offset in the x direction within the
+     *            texture array.
+     * @param yOffset Specifies a texel offset in the y direction within the
+     *            texture array.
+     * @param format The texture format (e.g. GL_RGBA)
+     * @param type The texture type (e.g. GL_UNSIGNED_BYTE)
+     */
+    public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset,
+            Bitmap bitmap,
+            int format, int type);
+
+    /**
+     * Generates buffers and uploads the buffer data.
+     *
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
+     */
+    public abstract int uploadBuffer(java.nio.FloatBuffer buffer);
+
+    /**
+     * Generates buffers and uploads the element array buffer data.
+     *
+     * @param buffer The buffer to upload
+     * @return The buffer ID that was generated.
+     */
+    public abstract int uploadBuffer(java.nio.ByteBuffer buffer);
+
+    /**
+     * After LightCycle makes GL calls, this method is called to restore the GL
+     * configuration to the one expected by GLCanvas.
+     */
+    public abstract void recoverFromLightCycle();
+
+    /**
+     * Gets the bounds given by x, y, width, and height as well as the internal
+     * matrix state. There is no special handling for non-90-degree rotations.
+     * It only considers the lower-left and upper-right corners as the bounds.
+     *
+     * @param bounds The output bounds to write to.
+     * @param x The left side of the input rectangle.
+     * @param y The bottom of the input rectangle.
+     * @param width The width of the input rectangle.
+     * @param height The height of the input rectangle.
+     */
+    public abstract void getBounds(Rect bounds, int x, int y, int width, int height);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES11Canvas.java b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java
new file mode 100644
index 0000000..7013c3d
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java
@@ -0,0 +1,997 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.opengl.GLU;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.IntArray;
+
+import junit.framework.Assert;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES11Canvas implements GLCanvas {
+    @SuppressWarnings("unused")
+    private static final String TAG = "GLCanvasImp";
+
+    private static final float OPAQUE_ALPHA = 0.95f;
+
+    private static final int OFFSET_FILL_RECT = 0;
+    private static final int OFFSET_DRAW_LINE = 4;
+    private static final int OFFSET_DRAW_RECT = 6;
+    private static final float[] BOX_COORDINATES = {
+            0, 0, 1, 0, 0, 1, 1, 1,  // used for filling a rectangle
+            0, 0, 1, 1,              // used for drawing a line
+            0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
+
+    private GL11 mGL;
+
+    private final float mMatrixValues[] = new float[16];
+    private final float mTextureMatrixValues[] = new float[16];
+
+    // The results of mapPoints are stored in this buffer, and the order is
+    // x1, y1, x2, y2.
+    private final float mMapPointsBuffer[] = new float[4];
+
+    private final float mTextureColor[] = new float[4];
+
+    private int mBoxCoords;
+
+    private GLState mGLState;
+    private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
+
+    private float mAlpha;
+    private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
+    private ConfigState mRecycledRestoreAction;
+
+    private final RectF mDrawTextureSourceRect = new RectF();
+    private final RectF mDrawTextureTargetRect = new RectF();
+    private final float[] mTempMatrix = new float[32];
+    private final IntArray mUnboundTextures = new IntArray();
+    private final IntArray mDeleteBuffers = new IntArray();
+    private int mScreenWidth;
+    private int mScreenHeight;
+    private boolean mBlendEnabled = true;
+    private int mFrameBuffer[] = new int[1];
+    private static float[] sCropRect = new float[4];
+
+    private RawTexture mTargetTexture;
+
+    // Drawing statistics
+    int mCountDrawLine;
+    int mCountFillRect;
+    int mCountDrawMesh;
+    int mCountTextureRect;
+    int mCountTextureOES;
+
+    private static GLId mGLId = new GLES11IdImpl();
+
+    public GLES11Canvas(GL11 gl) {
+        mGL = gl;
+        mGLState = new GLState(gl);
+        // First create an nio buffer, then create a VBO from it.
+        int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE;
+        FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+        xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0);
+
+        int[] name = new int[1];
+        mGLId.glGenBuffers(1, name, 0);
+        mBoxCoords = name[0];
+
+        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
+        gl.glBufferData(GL11.GL_ARRAY_BUFFER, xyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
+                xyBuffer, GL11.GL_STATIC_DRAW);
+
+        gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
+        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
+
+        // Enable the texture coordinate array for Texture 1
+        gl.glClientActiveTexture(GL11.GL_TEXTURE1);
+        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
+        gl.glClientActiveTexture(GL11.GL_TEXTURE0);
+        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+
+        // mMatrixValues and mAlpha will be initialized in setSize()
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        Assert.assertTrue(width >= 0 && height >= 0);
+
+        if (mTargetTexture == null) {
+            mScreenWidth = width;
+            mScreenHeight = height;
+        }
+        mAlpha = 1.0f;
+
+        GL11 gl = mGL;
+        gl.glViewport(0, 0, width, height);
+        gl.glMatrixMode(GL11.GL_PROJECTION);
+        gl.glLoadIdentity();
+        GLU.gluOrtho2D(gl, 0, width, 0, height);
+
+        gl.glMatrixMode(GL11.GL_MODELVIEW);
+        gl.glLoadIdentity();
+
+        float matrix[] = mMatrixValues;
+        Matrix.setIdentityM(matrix, 0);
+        // to match the graphic coordinate system in android, we flip it vertically.
+        if (mTargetTexture == null) {
+            Matrix.translateM(matrix, 0, 0, height, 0);
+            Matrix.scaleM(matrix, 0, 1, -1, 1);
+        }
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        Assert.assertTrue(alpha >= 0 && alpha <= 1);
+        mAlpha = alpha;
+    }
+
+    @Override
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    @Override
+    public void multiplyAlpha(float alpha) {
+        Assert.assertTrue(alpha >= 0 && alpha <= 1);
+        mAlpha *= alpha;
+    }
+
+    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+    }
+
+    @Override
+    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
+        GL11 gl = mGL;
+
+        mGLState.setColorMode(paint.getColor(), mAlpha);
+        mGLState.setLineWidth(paint.getLineWidth());
+
+        saveTransform();
+        translate(x, y);
+        scale(width, height, 1);
+
+        gl.glLoadMatrixf(mMatrixValues, 0);
+        gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4);
+
+        restoreTransform();
+        mCountDrawLine++;
+    }
+
+    @Override
+    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
+        GL11 gl = mGL;
+
+        mGLState.setColorMode(paint.getColor(), mAlpha);
+        mGLState.setLineWidth(paint.getLineWidth());
+
+        saveTransform();
+        translate(x1, y1);
+        scale(x2 - x1, y2 - y1, 1);
+
+        gl.glLoadMatrixf(mMatrixValues, 0);
+        gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2);
+
+        restoreTransform();
+        mCountDrawLine++;
+    }
+
+    @Override
+    public void fillRect(float x, float y, float width, float height, int color) {
+        mGLState.setColorMode(color, mAlpha);
+        GL11 gl = mGL;
+
+        saveTransform();
+        translate(x, y);
+        scale(width, height, 1);
+
+        gl.glLoadMatrixf(mMatrixValues, 0);
+        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
+
+        restoreTransform();
+        mCountFillRect++;
+    }
+
+    @Override
+    public void translate(float x, float y, float z) {
+        Matrix.translateM(mMatrixValues, 0, x, y, z);
+    }
+
+    // This is a faster version of translate(x, y, z) because
+    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+    // (3) we unroll the loop
+    @Override
+    public void translate(float x, float y) {
+        float[] m = mMatrixValues;
+        m[12] += m[0] * x + m[4] * y;
+        m[13] += m[1] * x + m[5] * y;
+        m[14] += m[2] * x + m[6] * y;
+        m[15] += m[3] * x + m[7] * y;
+    }
+
+    @Override
+    public void scale(float sx, float sy, float sz) {
+        Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
+    }
+
+    @Override
+    public void rotate(float angle, float x, float y, float z) {
+        if (angle == 0) return;
+        float[] temp = mTempMatrix;
+        Matrix.setRotateM(temp, 0, angle, x, y, z);
+        Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0);
+        System.arraycopy(temp, 16, mMatrixValues, 0, 16);
+    }
+
+    @Override
+    public void multiplyMatrix(float matrix[], int offset) {
+        float[] temp = mTempMatrix;
+        Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
+        System.arraycopy(temp, 0, mMatrixValues, 0, 16);
+    }
+
+    private void textureRect(float x, float y, float width, float height) {
+        GL11 gl = mGL;
+
+        saveTransform();
+        translate(x, y);
+        scale(width, height, 1);
+
+        gl.glLoadMatrixf(mMatrixValues, 0);
+        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
+
+        restoreTransform();
+        mCountTextureRect++;
+    }
+
+    @Override
+    public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
+            int uvBuffer, int indexBuffer, int indexCount) {
+        float alpha = mAlpha;
+        if (!bindTexture(tex)) return;
+
+        mGLState.setBlendEnabled(mBlendEnabled
+                && (!tex.isOpaque() || alpha < OPAQUE_ALPHA));
+        mGLState.setTextureAlpha(alpha);
+
+        // Reset the texture matrix. We will set our own texture coordinates
+        // below.
+        setTextureCoords(0, 0, 1, 1);
+
+        saveTransform();
+        translate(x, y);
+
+        mGL.glLoadMatrixf(mMatrixValues, 0);
+
+        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer);
+        mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
+
+        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer);
+        mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
+
+        mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+        mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
+                indexCount, GL11.GL_UNSIGNED_BYTE, 0);
+
+        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
+        mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
+        mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
+
+        restoreTransform();
+        mCountDrawMesh++;
+    }
+
+    // Transforms two points by the given matrix m. The result
+    // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
+    private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
+        float[] r = mMapPointsBuffer;
+
+        // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
+        float x3 = m[0] * x1 + m[4] * y1 + m[12];
+        float y3 = m[1] * x1 + m[5] * y1 + m[13];
+        float w3 = m[3] * x1 + m[7] * y1 + m[15];
+        r[0] = x3 / w3;
+        r[1] = y3 / w3;
+
+        // Same for x2 y2.
+        float x4 = m[0] * x2 + m[4] * y2 + m[12];
+        float y4 = m[1] * x2 + m[5] * y2 + m[13];
+        float w4 = m[3] * x2 + m[7] * y2 + m[15];
+        r[2] = x4 / w4;
+        r[3] = y4 / w4;
+
+        return r;
+    }
+
+    private void drawBoundTexture(
+            BasicTexture texture, int x, int y, int width, int height) {
+        // Test whether it has been rotated or flipped, if so, glDrawTexiOES
+        // won't work
+        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
+            if (texture.hasBorder()) {
+                setTextureCoords(
+                        1.0f / texture.getTextureWidth(),
+                        1.0f / texture.getTextureHeight(),
+                        (texture.getWidth() - 1.0f) / texture.getTextureWidth(),
+                        (texture.getHeight() - 1.0f) / texture.getTextureHeight());
+            } else {
+                setTextureCoords(0, 0,
+                        (float) texture.getWidth() / texture.getTextureWidth(),
+                        (float) texture.getHeight() / texture.getTextureHeight());
+            }
+            textureRect(x, y, width, height);
+        } else {
+            // draw the rect from bottom-left to top-right
+            float points[] = mapPoints(
+                    mMatrixValues, x, y + height, x + width, y);
+            x = (int) (points[0] + 0.5f);
+            y = (int) (points[1] + 0.5f);
+            width = (int) (points[2] + 0.5f) - x;
+            height = (int) (points[3] + 0.5f) - y;
+            if (width > 0 && height > 0) {
+                ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
+                mCountTextureOES++;
+            }
+        }
+    }
+
+    @Override
+    public void drawTexture(
+            BasicTexture texture, int x, int y, int width, int height) {
+        drawTexture(texture, x, y, width, height, mAlpha);
+    }
+
+    private void drawTexture(BasicTexture texture,
+            int x, int y, int width, int height, float alpha) {
+        if (width <= 0 || height <= 0) return;
+
+        mGLState.setBlendEnabled(mBlendEnabled
+                && (!texture.isOpaque() || alpha < OPAQUE_ALPHA));
+        if (!bindTexture(texture)) return;
+        mGLState.setTextureAlpha(alpha);
+        drawBoundTexture(texture, x, y, width, height);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) return;
+
+        // Copy the input to avoid changing it.
+        mDrawTextureSourceRect.set(source);
+        mDrawTextureTargetRect.set(target);
+        source = mDrawTextureSourceRect;
+        target = mDrawTextureTargetRect;
+
+        mGLState.setBlendEnabled(mBlendEnabled
+                && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
+        if (!bindTexture(texture)) return;
+        convertCoordinate(source, target, texture);
+        setTextureCoords(source);
+        mGLState.setTextureAlpha(mAlpha);
+        textureRect(target.left, target.top, target.width(), target.height());
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, float[] mTextureTransform,
+            int x, int y, int w, int h) {
+        mGLState.setBlendEnabled(mBlendEnabled
+                && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
+        if (!bindTexture(texture)) return;
+        setTextureCoords(mTextureTransform);
+        mGLState.setTextureAlpha(mAlpha);
+        textureRect(x, y, w, h);
+    }
+
+    // This function changes the source coordinate to the texture coordinates.
+    // It also clips the source and target coordinates if it is beyond the
+    // bound of the texture.
+    private static void convertCoordinate(RectF source, RectF target,
+            BasicTexture texture) {
+
+        int width = texture.getWidth();
+        int height = texture.getHeight();
+        int texWidth = texture.getTextureWidth();
+        int texHeight = texture.getTextureHeight();
+        // Convert to texture coordinates
+        source.left /= texWidth;
+        source.right /= texWidth;
+        source.top /= texHeight;
+        source.bottom /= texHeight;
+
+        // Clip if the rendering range is beyond the bound of the texture.
+        float xBound = (float) width / texWidth;
+        if (source.right > xBound) {
+            target.right = target.left + target.width() *
+                    (xBound - source.left) / source.width();
+            source.right = xBound;
+        }
+        float yBound = (float) height / texHeight;
+        if (source.bottom > yBound) {
+            target.bottom = target.top + target.height() *
+                    (yBound - source.top) / source.height();
+            source.bottom = yBound;
+        }
+    }
+
+    @Override
+    public void drawMixed(BasicTexture from,
+            int toColor, float ratio, int x, int y, int w, int h) {
+        drawMixed(from, toColor, ratio, x, y, w, h, mAlpha);
+    }
+
+    private boolean bindTexture(BasicTexture texture) {
+        if (!texture.onBind(this)) return false;
+        int target = texture.getTarget();
+        mGLState.setTextureTarget(target);
+        mGL.glBindTexture(target, texture.getId());
+        return true;
+    }
+
+    private void setTextureColor(float r, float g, float b, float alpha) {
+        float[] color = mTextureColor;
+        color[0] = r;
+        color[1] = g;
+        color[2] = b;
+        color[3] = alpha;
+    }
+
+    private void setMixedColor(int toColor, float ratio, float alpha) {
+        //
+        // The formula we want:
+        //     alpha * ((1 - ratio) * from + ratio * to)
+        //
+        // The formula that GL supports is in the form of:
+        //     combo * from + (1 - combo) * to * scale
+        //
+        // So, we have combo = alpha * (1 - ratio)
+        //     and     scale = alpha * ratio / (1 - combo)
+        //
+        float combo = alpha * (1 - ratio);
+        float scale = alpha * ratio / (1 - combo);
+
+        // Specify the interpolation factor via the alpha component of
+        // GL_TEXTURE_ENV_COLORs.
+        // RGB component are get from toColor and will used as SRC1
+        float colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
+        setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
+                ((toColor >>> 8) & 0xff) * colorScale,
+                (toColor & 0xff) * colorScale, combo);
+        GL11 gl = mGL;
+        gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
+
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
+
+        // Wire up the interpolation factor for RGB.
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
+
+        // Wire up the interpolation factor for alpha.
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT);
+        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
+
+    }
+
+    @Override
+    public void drawMixed(BasicTexture from, int toColor, float ratio,
+            RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) return;
+
+        if (ratio <= 0.01f) {
+            drawTexture(from, source, target);
+            return;
+        } else if (ratio >= 1) {
+            fillRect(target.left, target.top, target.width(), target.height(), toColor);
+            return;
+        }
+
+        float alpha = mAlpha;
+
+        // Copy the input to avoid changing it.
+        mDrawTextureSourceRect.set(source);
+        mDrawTextureTargetRect.set(target);
+        source = mDrawTextureSourceRect;
+        target = mDrawTextureTargetRect;
+
+        mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
+                || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
+
+        if (!bindTexture(from)) return;
+
+        // Interpolate the RGB and alpha values between both textures.
+        mGLState.setTexEnvMode(GL11.GL_COMBINE);
+        setMixedColor(toColor, ratio, alpha);
+        convertCoordinate(source, target, from);
+        setTextureCoords(source);
+        textureRect(target.left, target.top, target.width(), target.height());
+        mGLState.setTexEnvMode(GL11.GL_REPLACE);
+    }
+
+    private void drawMixed(BasicTexture from, int toColor,
+            float ratio, int x, int y, int width, int height, float alpha) {
+        // change from 0 to 0.01f to prevent getting divided by zero below
+        if (ratio <= 0.01f) {
+            drawTexture(from, x, y, width, height, alpha);
+            return;
+        } else if (ratio >= 1) {
+            fillRect(x, y, width, height, toColor);
+            return;
+        }
+
+        mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
+                || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
+
+        final GL11 gl = mGL;
+        if (!bindTexture(from)) return;
+
+        // Interpolate the RGB and alpha values between both textures.
+        mGLState.setTexEnvMode(GL11.GL_COMBINE);
+        setMixedColor(toColor, ratio, alpha);
+
+        drawBoundTexture(from, x, y, width, height);
+        mGLState.setTexEnvMode(GL11.GL_REPLACE);
+    }
+
+    // TODO: the code only work for 2D should get fixed for 3D or removed
+    private static final int MSKEW_X = 4;
+    private static final int MSKEW_Y = 1;
+    private static final int MSCALE_X = 0;
+    private static final int MSCALE_Y = 5;
+
+    private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
+        final float eps = 1e-5f;
+        return Math.abs(matrix[MSKEW_X]) > eps
+                || Math.abs(matrix[MSKEW_Y]) > eps
+                || matrix[MSCALE_X] < -eps
+                || matrix[MSCALE_Y] > eps;
+    }
+
+    private static class GLState {
+
+        private final GL11 mGL;
+
+        private int mTexEnvMode = GL11.GL_REPLACE;
+        private float mTextureAlpha = 1.0f;
+        private int mTextureTarget = GL11.GL_TEXTURE_2D;
+        private boolean mBlendEnabled = true;
+        private float mLineWidth = 1.0f;
+        private boolean mLineSmooth = false;
+
+        public GLState(GL11 gl) {
+            mGL = gl;
+
+            // Disable unused state
+            gl.glDisable(GL11.GL_LIGHTING);
+
+            // Enable used features
+            gl.glEnable(GL11.GL_DITHER);
+
+            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
+            gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
+            gl.glEnable(GL11.GL_TEXTURE_2D);
+
+            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
+                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
+
+            // Set the background color
+            gl.glClearColor(0f, 0f, 0f, 0f);
+
+            gl.glEnable(GL11.GL_BLEND);
+            gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
+
+            // We use 565 or 8888 format, so set the alignment to 2 bytes/pixel.
+            gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
+        }
+
+        public void setTexEnvMode(int mode) {
+            if (mTexEnvMode == mode) return;
+            mTexEnvMode = mode;
+            mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode);
+        }
+
+        public void setLineWidth(float width) {
+            if (mLineWidth == width) return;
+            mLineWidth = width;
+            mGL.glLineWidth(width);
+        }
+
+        public void setTextureAlpha(float alpha) {
+            if (mTextureAlpha == alpha) return;
+            mTextureAlpha = alpha;
+            if (alpha >= OPAQUE_ALPHA) {
+                // The alpha is need for those texture without alpha channel
+                mGL.glColor4f(1, 1, 1, 1);
+                setTexEnvMode(GL11.GL_REPLACE);
+            } else {
+                mGL.glColor4f(alpha, alpha, alpha, alpha);
+                setTexEnvMode(GL11.GL_MODULATE);
+            }
+        }
+
+        public void setColorMode(int color, float alpha) {
+            setBlendEnabled(!Utils.isOpaque(color) || alpha < OPAQUE_ALPHA);
+
+            // Set mTextureAlpha to an invalid value, so that it will reset
+            // again in setTextureAlpha(float) later.
+            mTextureAlpha = -1.0f;
+
+            setTextureTarget(0);
+
+            float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f;
+            mGL.glColor4x(
+                    Math.round(((color >> 16) & 0xFF) * prealpha),
+                    Math.round(((color >> 8) & 0xFF) * prealpha),
+                    Math.round((color & 0xFF) * prealpha),
+                    Math.round(255 * prealpha));
+        }
+
+        // target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
+        public void setTextureTarget(int target) {
+            if (mTextureTarget == target) return;
+            if (mTextureTarget != 0) {
+                mGL.glDisable(mTextureTarget);
+            }
+            mTextureTarget = target;
+            if (mTextureTarget != 0) {
+                mGL.glEnable(mTextureTarget);
+            }
+        }
+
+        public void setBlendEnabled(boolean enabled) {
+            if (mBlendEnabled == enabled) return;
+            mBlendEnabled = enabled;
+            if (enabled) {
+                mGL.glEnable(GL11.GL_BLEND);
+            } else {
+                mGL.glDisable(GL11.GL_BLEND);
+            }
+        }
+    }
+
+    @Override
+    public void clearBuffer(float[] argb) {
+        if(argb != null && argb.length == 4) {
+            mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+        } else {
+            mGL.glClearColor(0, 0, 0, 1);
+        }
+        mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
+    }
+
+    @Override
+    public void clearBuffer() {
+        clearBuffer(null);
+    }
+
+    private void setTextureCoords(RectF source) {
+        setTextureCoords(source.left, source.top, source.right, source.bottom);
+    }
+
+    private void setTextureCoords(float left, float top,
+            float right, float bottom) {
+        mGL.glMatrixMode(GL11.GL_TEXTURE);
+        mTextureMatrixValues[0] = right - left;
+        mTextureMatrixValues[5] = bottom - top;
+        mTextureMatrixValues[10] = 1;
+        mTextureMatrixValues[12] = left;
+        mTextureMatrixValues[13] = top;
+        mTextureMatrixValues[15] = 1;
+        mGL.glLoadMatrixf(mTextureMatrixValues, 0);
+        mGL.glMatrixMode(GL11.GL_MODELVIEW);
+    }
+
+    private void setTextureCoords(float[] mTextureTransform) {
+        mGL.glMatrixMode(GL11.GL_TEXTURE);
+        mGL.glLoadMatrixf(mTextureTransform, 0);
+        mGL.glMatrixMode(GL11.GL_MODELVIEW);
+    }
+
+    // unloadTexture and deleteBuffer can be called from the finalizer thread,
+    // so we synchronized on the mUnboundTextures object.
+    @Override
+    public boolean unloadTexture(BasicTexture t) {
+        synchronized (mUnboundTextures) {
+            if (!t.isLoaded()) return false;
+            mUnboundTextures.add(t.mId);
+            return true;
+        }
+    }
+
+    @Override
+    public void deleteBuffer(int bufferId) {
+        synchronized (mUnboundTextures) {
+            mDeleteBuffers.add(bufferId);
+        }
+    }
+
+    @Override
+    public void deleteRecycledResources() {
+        synchronized (mUnboundTextures) {
+            IntArray ids = mUnboundTextures;
+            if (ids.size() > 0) {
+                mGLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+
+            ids = mDeleteBuffers;
+            if (ids.size() > 0) {
+                mGLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+        }
+    }
+
+    @Override
+    public void save() {
+        save(SAVE_FLAG_ALL);
+    }
+
+    @Override
+    public void save(int saveFlags) {
+        ConfigState config = obtainRestoreConfig();
+
+        if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
+            config.mAlpha = mAlpha;
+        } else {
+            config.mAlpha = -1;
+        }
+
+        if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
+            System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
+        } else {
+            config.mMatrix[0] = Float.NEGATIVE_INFINITY;
+        }
+
+        mRestoreStack.add(config);
+    }
+
+    @Override
+    public void restore() {
+        if (mRestoreStack.isEmpty()) throw new IllegalStateException();
+        ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
+        config.restore(this);
+        freeRestoreConfig(config);
+    }
+
+    private void freeRestoreConfig(ConfigState action) {
+        action.mNextFree = mRecycledRestoreAction;
+        mRecycledRestoreAction = action;
+    }
+
+    private ConfigState obtainRestoreConfig() {
+        if (mRecycledRestoreAction != null) {
+            ConfigState result = mRecycledRestoreAction;
+            mRecycledRestoreAction = result.mNextFree;
+            return result;
+        }
+        return new ConfigState();
+    }
+
+    private static class ConfigState {
+        float mAlpha;
+        float mMatrix[] = new float[16];
+        ConfigState mNextFree;
+
+        public void restore(GLES11Canvas canvas) {
+            if (mAlpha >= 0) canvas.setAlpha(mAlpha);
+            if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
+                System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
+            }
+        }
+    }
+
+    @Override
+    public void dumpStatisticsAndClear() {
+        String line = String.format(
+                "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d",
+                mCountDrawMesh, mCountTextureRect, mCountTextureOES,
+                mCountFillRect, mCountDrawLine);
+        mCountDrawMesh = 0;
+        mCountTextureRect = 0;
+        mCountTextureOES = 0;
+        mCountFillRect = 0;
+        mCountDrawLine = 0;
+        Log.d(TAG, line);
+    }
+
+    private void saveTransform() {
+        System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16);
+    }
+
+    private void restoreTransform() {
+        System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16);
+    }
+
+    private void setRenderTarget(RawTexture texture) {
+        GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
+
+        if (mTargetTexture == null && texture != null) {
+            mGLId.glGenBuffers(1, mFrameBuffer, 0);
+            gl11ep.glBindFramebufferOES(
+                    GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
+        }
+        if (mTargetTexture != null && texture  == null) {
+            gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
+            gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
+        }
+
+        mTargetTexture = texture;
+        if (texture == null) {
+            setSize(mScreenWidth, mScreenHeight);
+        } else {
+            setSize(texture.getWidth(), texture.getHeight());
+
+            if (!texture.isLoaded()) texture.prepare(this);
+
+            gl11ep.glFramebufferTexture2DOES(
+                    GL11ExtensionPack.GL_FRAMEBUFFER_OES,
+                    GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
+                    GL11.GL_TEXTURE_2D, texture.getId(), 0);
+
+            checkFramebufferStatus(gl11ep);
+        }
+    }
+
+    @Override
+    public void endRenderTarget() {
+        RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
+        setRenderTarget(texture);
+        restore(); // restore matrix and alpha
+    }
+
+    @Override
+    public void beginRenderTarget(RawTexture texture) {
+        save(); // save matrix and alpha
+        mTargetStack.add(mTargetTexture);
+        setRenderTarget(texture);
+    }
+
+    private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
+        int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
+        if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
+            String msg = "";
+            switch (status) {
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+                    msg = "FRAMEBUFFER_FORMATS";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+                    msg = "FRAMEBUFFER_ATTACHMENT";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+                    msg = "FRAMEBUFFER_MISSING_ATTACHMENT";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
+                    msg = "FRAMEBUFFER_DRAW_BUFFER";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
+                    msg = "FRAMEBUFFER_READ_BUFFER";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
+                    msg = "FRAMEBUFFER_UNSUPPORTED";
+                    break;
+                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+                    msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+                    break;
+            }
+            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+        }
+    }
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+        int width = texture.getWidth();
+        int height = texture.getHeight();
+        // Define a vertically flipped crop rectangle for OES_draw_texture.
+        // The four values in sCropRect are: left, bottom, width, and
+        // height. Negative value of width or height means flip.
+        sCropRect[0] = 0;
+        sCropRect[1] = height;
+        sCropRect[2] = width;
+        sCropRect[3] = -height;
+
+        // Set texture parameters.
+        int target = texture.getTarget();
+        mGL.glBindTexture(target, texture.getId());
+        mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
+        mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
+        mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
+        mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+        mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+    }
+
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+        int target = texture.getTarget();
+        mGL.glBindTexture(target, texture.getId());
+        int width = texture.getTextureWidth();
+        int height = texture.getTextureHeight();
+        mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+    }
+
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+        int target = texture.getTarget();
+        mGL.glBindTexture(target, texture.getId());
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+    }
+
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+        int target = texture.getTarget();
+        mGL.glBindTexture(target, texture.getId());
+        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+    }
+
+    @Override
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, Float.SIZE / Byte.SIZE);
+    }
+
+    @Override
+    public int uploadBuffer(ByteBuffer buf) {
+        return uploadBuffer(buf, 1);
+    }
+
+    private int uploadBuffer(Buffer buf, int elementSize) {
+        int[] bufferIds = new int[1];
+        mGLId.glGenBuffers(bufferIds.length, bufferIds, 0);
+        int bufferId = bufferIds[0];
+        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId);
+        mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf,
+                GL11.GL_STATIC_DRAW);
+        return bufferId;
+    }
+
+    @Override
+    public void recoverFromLightCycle() {
+        // This is only required for GLES20
+    }
+
+    @Override
+    public void getBounds(Rect bounds, int x, int y, int width, int height) {
+        // This is only required for GLES20
+    }
+
+    @Override
+    public GLId getGLId() {
+        return mGLId;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java
new file mode 100644
index 0000000..e479373
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES11IdImpl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+/**
+ * Open GL ES 1.1 implementation for generating and destroying texture IDs and
+ * buffer IDs
+ */
+public class GLES11IdImpl implements GLId {
+    private static int sNextId = 1;
+    // Mutex for sNextId
+    private static Object sLock = new Object();
+
+    @Override
+    public int generateTexture() {
+        synchronized (sLock) {
+            return sNextId++;
+        }
+    }
+
+    @Override
+    public void glGenBuffers(int n, int[] buffers, int offset) {
+        synchronized (sLock) {
+            while (n-- > 0) {
+                buffers[offset + n] = sNextId++;
+            }
+        }
+    }
+
+    @Override
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+        synchronized (sLock) {
+            gl.glDeleteTextures(n, textures, offset);
+        }
+    }
+
+    @Override
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+        synchronized (sLock) {
+            gl.glDeleteBuffers(n, buffers, offset);
+        }
+    }
+
+    @Override
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+        synchronized (sLock) {
+            gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
+        }
+    }
+
+
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
new file mode 100644
index 0000000..4ead131
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.opengl.GLES20;
+import android.opengl.GLUtils;
+import android.opengl.Matrix;
+import android.util.Log;
+
+import com.android.gallery3d.util.IntArray;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class GLES20Canvas implements GLCanvas {
+    // ************** Constants **********************
+    private static final String TAG = GLES20Canvas.class.getSimpleName();
+    private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE;
+    private static final float OPAQUE_ALPHA = 0.95f;
+
+    private static final int COORDS_PER_VERTEX = 2;
+    private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE;
+
+    private static final int COUNT_FILL_VERTEX = 4;
+    private static final int COUNT_LINE_VERTEX = 2;
+    private static final int COUNT_RECT_VERTEX = 4;
+    private static final int OFFSET_FILL_RECT = 0;
+    private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX;
+    private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX;
+
+    private static final float[] BOX_COORDINATES = {
+            0, 0, // Fill rectangle
+            1, 0,
+            0, 1,
+            1, 1,
+            0, 0, // Draw line
+            1, 1,
+            0, 0, // Draw rectangle outline
+            0, 1,
+            1, 1,
+            1, 0,
+    };
+
+    private static final float[] BOUNDS_COORDINATES = {
+        0, 0, 0, 1,
+        1, 1, 0, 1,
+    };
+
+    private static final String POSITION_ATTRIBUTE = "aPosition";
+    private static final String COLOR_UNIFORM = "uColor";
+    private static final String MATRIX_UNIFORM = "uMatrix";
+    private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
+    private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";
+    private static final String ALPHA_UNIFORM = "uAlpha";
+    private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate";
+
+    private static final String DRAW_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "}\n";
+
+    private static final String DRAW_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "uniform vec4 " + COLOR_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = " + COLOR_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final String TEXTURE_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "  vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n"
+            + "}\n";
+
+    private static final String MESH_VERTEX_SHADER = ""
+            + "uniform mat4 " + MATRIX_UNIFORM + ";\n"
+            + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n"
+            + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "void main() {\n"
+            + "  vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n"
+            + "  gl_Position = " + MATRIX_UNIFORM + " * pos;\n"
+            + "  vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n"
+            + "}\n";
+
+    private static final String TEXTURE_FRAGMENT_SHADER = ""
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+            + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final String OES_TEXTURE_FRAGMENT_SHADER = ""
+            + "#extension GL_OES_EGL_image_external : require\n"
+            + "precision mediump float;\n"
+            + "varying vec2 vTextureCoord;\n"
+            + "uniform float " + ALPHA_UNIFORM + ";\n"
+            + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n"
+            + "void main() {\n"
+            + "  gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n"
+            + "  gl_FragColor *= " + ALPHA_UNIFORM + ";\n"
+            + "}\n";
+
+    private static final int INITIAL_RESTORE_STATE_SIZE = 8;
+    private static final int MATRIX_SIZE = 16;
+
+    // Keep track of restore state
+    private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE];
+    private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE];
+    private IntArray mSaveFlags = new IntArray();
+
+    private int mCurrentAlphaIndex = 0;
+    private int mCurrentMatrixIndex = 0;
+
+    // Viewport size
+    private int mWidth;
+    private int mHeight;
+
+    // Projection matrix
+    private float[] mProjectionMatrix = new float[MATRIX_SIZE];
+
+    // Screen size for when we aren't bound to a texture
+    private int mScreenWidth;
+    private int mScreenHeight;
+
+    // GL programs
+    private int mDrawProgram;
+    private int mTextureProgram;
+    private int mOesTextureProgram;
+    private int mMeshProgram;
+
+    // GL buffer containing BOX_COORDINATES
+    private int mBoxCoordinates;
+
+    // Handle indices -- common
+    private static final int INDEX_POSITION = 0;
+    private static final int INDEX_MATRIX = 1;
+
+    // Handle indices -- draw
+    private static final int INDEX_COLOR = 2;
+
+    // Handle indices -- texture
+    private static final int INDEX_TEXTURE_MATRIX = 2;
+    private static final int INDEX_TEXTURE_SAMPLER = 3;
+    private static final int INDEX_ALPHA = 4;
+
+    // Handle indices -- mesh
+    private static final int INDEX_TEXTURE_COORD = 2;
+
+    private abstract static class ShaderParameter {
+        public int handle;
+        protected final String mName;
+
+        public ShaderParameter(String name) {
+            mName = name;
+        }
+
+        public abstract void loadHandle(int program);
+    }
+
+    private static class UniformShaderParameter extends ShaderParameter {
+        public UniformShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetUniformLocation(program, mName);
+            checkError();
+        }
+    }
+
+    private static class AttributeShaderParameter extends ShaderParameter {
+        public AttributeShaderParameter(String name) {
+            super(name);
+        }
+
+        @Override
+        public void loadHandle(int program) {
+            handle = GLES20.glGetAttribLocation(program, mName);
+            checkError();
+        }
+    }
+
+    ShaderParameter[] mDrawParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR
+    };
+    ShaderParameter[] mTextureParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+    ShaderParameter[] mOesTextureParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+    ShaderParameter[] mMeshParameters = {
+            new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION
+            new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX
+            new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD
+            new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER
+            new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA
+    };
+
+    private final IntArray mUnboundTextures = new IntArray();
+    private final IntArray mDeleteBuffers = new IntArray();
+
+    // Keep track of statistics for debugging
+    private int mCountDrawMesh = 0;
+    private int mCountTextureRect = 0;
+    private int mCountFillRect = 0;
+    private int mCountDrawLine = 0;
+
+    // Buffer for framebuffer IDs -- we keep track so we can switch the attached
+    // texture.
+    private int[] mFrameBuffer = new int[1];
+
+    // Bound textures.
+    private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>();
+
+    // Temporary variables used within calculations
+    private final float[] mTempMatrix = new float[32];
+    private final float[] mTempColor = new float[4];
+    private final RectF mTempSourceRect = new RectF();
+    private final RectF mTempTargetRect = new RectF();
+    private final float[] mTempTextureMatrix = new float[MATRIX_SIZE];
+    private final int[] mTempIntArray = new int[1];
+
+    private static final GLId mGLId = new GLES20IdImpl();
+
+    public GLES20Canvas() {
+        Matrix.setIdentityM(mTempTextureMatrix, 0);
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+        mAlphas[mCurrentAlphaIndex] = 1f;
+        mTargetTextures.add(null);
+
+        FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES);
+        mBoxCoordinates = uploadBuffer(boxBuffer);
+
+        int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER);
+        int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER);
+        int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER);
+        int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER);
+        int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER);
+        int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER,
+                OES_TEXTURE_FRAGMENT_SHADER);
+
+        mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters);
+        mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader,
+                mTextureParameters);
+        mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader,
+                mOesTextureParameters);
+        mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters);
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        checkError();
+    }
+
+    private static FloatBuffer createBuffer(float[] values) {
+        // First create an nio buffer, then create a VBO from it.
+        int size = values.length * FLOAT_SIZE;
+        FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder())
+                .asFloatBuffer();
+        buffer.put(values, 0, values.length).position(0);
+        return buffer;
+    }
+
+    private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) {
+        int program = GLES20.glCreateProgram();
+        checkError();
+        if (program == 0) {
+            throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError());
+        }
+        GLES20.glAttachShader(program, vertexShader);
+        checkError();
+        GLES20.glAttachShader(program, fragmentShader);
+        checkError();
+        GLES20.glLinkProgram(program);
+        checkError();
+        int[] mLinkStatus = mTempIntArray;
+        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0);
+        if (mLinkStatus[0] != GLES20.GL_TRUE) {
+            Log.e(TAG, "Could not link program: ");
+            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+            GLES20.glDeleteProgram(program);
+            program = 0;
+        }
+        for (int i = 0; i < params.length; i++) {
+            params[i].loadHandle(program);
+        }
+        return program;
+    }
+
+    private static int loadShader(int type, String shaderCode) {
+        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
+        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
+        int shader = GLES20.glCreateShader(type);
+
+        // add the source code to the shader and compile it
+        GLES20.glShaderSource(shader, shaderCode);
+        checkError();
+        GLES20.glCompileShader(shader);
+        checkError();
+
+        return shader;
+    }
+
+    @Override
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        checkError();
+        Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex);
+        Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1);
+        if (getTargetTexture() == null) {
+            mScreenWidth = width;
+            mScreenHeight = height;
+            Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0);
+            Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1);
+        }
+    }
+
+    @Override
+    public void clearBuffer() {
+        GLES20.glClearColor(0f, 0f, 0f, 1f);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    @Override
+    public void clearBuffer(float[] argb) {
+        GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]);
+        checkError();
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        checkError();
+    }
+
+    @Override
+    public float getAlpha() {
+        return mAlphas[mCurrentAlphaIndex];
+    }
+
+    @Override
+    public void setAlpha(float alpha) {
+        mAlphas[mCurrentAlphaIndex] = alpha;
+    }
+
+    @Override
+    public void multiplyAlpha(float alpha) {
+        setAlpha(getAlpha() * alpha);
+    }
+
+    @Override
+    public void translate(float x, float y, float z) {
+        Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z);
+    }
+
+    // This is a faster version of translate(x, y, z) because
+    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
+    // (3) we unroll the loop
+    @Override
+    public void translate(float x, float y) {
+        int index = mCurrentMatrixIndex;
+        float[] m = mMatrices;
+        m[index + 12] += m[index + 0] * x + m[index + 4] * y;
+        m[index + 13] += m[index + 1] * x + m[index + 5] * y;
+        m[index + 14] += m[index + 2] * x + m[index + 6] * y;
+        m[index + 15] += m[index + 3] * x + m[index + 7] * y;
+    }
+
+    @Override
+    public void scale(float sx, float sy, float sz) {
+        Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz);
+    }
+
+    @Override
+    public void rotate(float angle, float x, float y, float z) {
+        if (angle == 0f) {
+            return;
+        }
+        float[] temp = mTempMatrix;
+        Matrix.setRotateM(temp, 0, angle, x, y, z);
+        float[] matrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0);
+        System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE);
+    }
+
+    @Override
+    public void multiplyMatrix(float[] matrix, int offset) {
+        float[] temp = mTempMatrix;
+        float[] currentMatrix = mMatrices;
+        int index = mCurrentMatrixIndex;
+        Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset);
+        System.arraycopy(temp, 0, currentMatrix, index, 16);
+    }
+
+    @Override
+    public void save() {
+        save(SAVE_FLAG_ALL);
+    }
+
+    @Override
+    public void save(int saveFlags) {
+        boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (saveAlpha) {
+            float currentAlpha = getAlpha();
+            mCurrentAlphaIndex++;
+            if (mAlphas.length <= mCurrentAlphaIndex) {
+                mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2);
+            }
+            mAlphas[mCurrentAlphaIndex] = currentAlpha;
+        }
+        boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (saveMatrix) {
+            int currentIndex = mCurrentMatrixIndex;
+            mCurrentMatrixIndex += MATRIX_SIZE;
+            if (mMatrices.length <= mCurrentMatrixIndex) {
+                mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2);
+            }
+            System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE);
+        }
+        mSaveFlags.add(saveFlags);
+    }
+
+    @Override
+    public void restore() {
+        int restoreFlags = mSaveFlags.removeLast();
+        boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA;
+        if (restoreAlpha) {
+            mCurrentAlphaIndex--;
+        }
+        boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX;
+        if (restoreMatrix) {
+            mCurrentMatrixIndex -= MATRIX_SIZE;
+        }
+    }
+
+    @Override
+    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
+        draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1,
+                paint);
+        mCountDrawLine++;
+    }
+
+    @Override
+    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
+        draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint);
+        mCountDrawLine++;
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            GLPaint paint) {
+        draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth());
+    }
+
+    private void draw(int type, int offset, int count, float x, float y, float width, float height,
+            int color, float lineWidth) {
+        prepareDraw(offset, color, lineWidth);
+        draw(mDrawParameters, type, count, x, y, width, height);
+    }
+
+    private void prepareDraw(int offset, int color, float lineWidth) {
+        GLES20.glUseProgram(mDrawProgram);
+        checkError();
+        if (lineWidth > 0) {
+            GLES20.glLineWidth(lineWidth);
+            checkError();
+        }
+        float[] colorArray = getColor(color);
+        boolean blendingEnabled = (colorArray[3] < 1f);
+        enableBlending(blendingEnabled);
+        if (blendingEnabled) {
+            GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]);
+            checkError();
+        }
+
+        GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0);
+        setPosition(mDrawParameters, offset);
+        checkError();
+    }
+
+    private float[] getColor(int color) {
+        float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha();
+        float red = ((color >>> 16) & 0xFF) / 255f * alpha;
+        float green = ((color >>> 8) & 0xFF) / 255f * alpha;
+        float blue = (color & 0xFF) / 255f * alpha;
+        mTempColor[0] = red;
+        mTempColor[1] = green;
+        mTempColor[2] = blue;
+        mTempColor[3] = alpha;
+        return mTempColor;
+    }
+
+    private void enableBlending(boolean enableBlending) {
+        if (enableBlending) {
+            GLES20.glEnable(GLES20.GL_BLEND);
+            checkError();
+        } else {
+            GLES20.glDisable(GLES20.GL_BLEND);
+            checkError();
+        }
+    }
+
+    private void setPosition(ShaderParameter[] params, int offset) {
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates);
+        checkError();
+        GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX,
+                GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+        checkError();
+    }
+
+    private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width,
+            float height) {
+        setMatrix(params, x, y, width, height);
+        int positionHandle = params[INDEX_POSITION].handle;
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDrawArrays(type, 0, count);
+        checkError();
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+    }
+
+    private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) {
+        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+        Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0);
+        GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE);
+        checkError();
+    }
+
+    @Override
+    public void fillRect(float x, float y, float width, float height, int color) {
+        draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height,
+                color, 0f);
+        mCountFillRect++;
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, int x, int y, int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return;
+        }
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + width, y + height);
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) {
+        int left = 0;
+        int top = 0;
+        int right = texture.getWidth();
+        int bottom = texture.getHeight();
+        if (texture.hasBorder()) {
+            left = 1;
+            top = 1;
+            right -= 1;
+            bottom -= 1;
+        }
+        outRect.set(left, top, right, bottom);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        mTempSourceRect.set(source);
+        mTempTargetRect.set(target);
+
+        convertCoordinate(mTempSourceRect, mTempTargetRect, texture);
+        drawTextureRect(texture, mTempSourceRect, mTempTargetRect);
+    }
+
+    @Override
+    public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w,
+            int h) {
+        if (w <= 0 || h <= 0) {
+            return;
+        }
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawTextureRect(texture, textureTransform, mTempTargetRect);
+    }
+
+    private void drawTextureRect(BasicTexture texture, RectF source, RectF target) {
+        setTextureMatrix(source);
+        drawTextureRect(texture, mTempTextureMatrix, target);
+    }
+
+    private void setTextureMatrix(RectF source) {
+        mTempTextureMatrix[0] = source.width();
+        mTempTextureMatrix[5] = source.height();
+        mTempTextureMatrix[12] = source.left;
+        mTempTextureMatrix[13] = source.top;
+    }
+
+    // This function changes the source coordinate to the texture coordinates.
+    // It also clips the source and target coordinates if it is beyond the
+    // bound of the texture.
+    private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) {
+        int width = texture.getWidth();
+        int height = texture.getHeight();
+        int texWidth = texture.getTextureWidth();
+        int texHeight = texture.getTextureHeight();
+        // Convert to texture coordinates
+        source.left /= texWidth;
+        source.right /= texWidth;
+        source.top /= texHeight;
+        source.bottom /= texHeight;
+
+        // Clip if the rendering range is beyond the bound of the texture.
+        float xBound = (float) width / texWidth;
+        if (source.right > xBound) {
+            target.right = target.left + target.width() * (xBound - source.left) / source.width();
+            source.right = xBound;
+        }
+        float yBound = (float) height / texHeight;
+        if (source.bottom > yBound) {
+            target.bottom = target.top + target.height() * (yBound - source.top) / source.height();
+            source.bottom = yBound;
+        }
+    }
+
+    private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) {
+        ShaderParameter[] params = prepareTexture(texture);
+        setPosition(params, OFFSET_FILL_RECT);
+        GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0);
+        checkError();
+        if (texture.isFlippedVertically()) {
+            save(SAVE_FLAG_MATRIX);
+            translate(0, target.centerY());
+            scale(1, -1, 1);
+            translate(0, -target.centerY());
+        }
+        draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top,
+                target.width(), target.height());
+        if (texture.isFlippedVertically()) {
+            restore();
+        }
+        mCountTextureRect++;
+    }
+
+    private ShaderParameter[] prepareTexture(BasicTexture texture) {
+        ShaderParameter[] params;
+        int program;
+        if (texture.getTarget() == GLES20.GL_TEXTURE_2D) {
+            params = mTextureParameters;
+            program = mTextureProgram;
+        } else {
+            params = mOesTextureParameters;
+            program = mOesTextureProgram;
+        }
+        prepareTexture(texture, program, params);
+        return params;
+    }
+
+    private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
+        GLES20.glUseProgram(program);
+        checkError();
+        enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        checkError();
+        texture.onBind(this);
+        GLES20.glBindTexture(texture.getTarget(), texture.getId());
+        checkError();
+        GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0);
+        checkError();
+        GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha());
+        checkError();
+    }
+
+    @Override
+    public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer,
+            int indexBuffer, int indexCount) {
+        prepareTexture(texture, mMeshProgram, mMeshParameters);
+
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer);
+        checkError();
+        int positionHandle = mMeshParameters[INDEX_POSITION].handle;
+        GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false,
+                VERTEX_STRIDE, 0);
+        checkError();
+
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer);
+        checkError();
+        int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle;
+        GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+                false, VERTEX_STRIDE, 0);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
+        checkError();
+
+        GLES20.glEnableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glEnableVertexAttribArray(texCoordHandle);
+        checkError();
+
+        setMatrix(mMeshParameters, x, y, 1, 1);
+        GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0);
+        checkError();
+
+        GLES20.glDisableVertexAttribArray(positionHandle);
+        checkError();
+        GLES20.glDisableVertexAttribArray(texCoordHandle);
+        checkError();
+        GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0);
+        checkError();
+        mCountDrawMesh++;
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) {
+        copyTextureCoordinates(texture, mTempSourceRect);
+        mTempTargetRect.set(x, y, x + w, y + h);
+        drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect);
+    }
+
+    @Override
+    public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) {
+        if (target.width() <= 0 || target.height() <= 0) {
+            return;
+        }
+        save(SAVE_FLAG_ALPHA);
+
+        float currentAlpha = getAlpha();
+        float cappedRatio = Math.min(1f, Math.max(0f, ratio));
+
+        float textureAlpha = (1f - cappedRatio) * currentAlpha;
+        setAlpha(textureAlpha);
+        drawTexture(texture, source, target);
+
+        float colorAlpha = cappedRatio * currentAlpha;
+        setAlpha(colorAlpha);
+        fillRect(target.left, target.top, target.width(), target.height(), toColor);
+
+        restore();
+    }
+
+    @Override
+    public boolean unloadTexture(BasicTexture texture) {
+        boolean unload = texture.isLoaded();
+        if (unload) {
+            synchronized (mUnboundTextures) {
+                mUnboundTextures.add(texture.getId());
+            }
+        }
+        return unload;
+    }
+
+    @Override
+    public void deleteBuffer(int bufferId) {
+        synchronized (mUnboundTextures) {
+            mDeleteBuffers.add(bufferId);
+        }
+    }
+
+    @Override
+    public void deleteRecycledResources() {
+        synchronized (mUnboundTextures) {
+            IntArray ids = mUnboundTextures;
+            if (mUnboundTextures.size() > 0) {
+                mGLId.glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+
+            ids = mDeleteBuffers;
+            if (ids.size() > 0) {
+                mGLId.glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0);
+                ids.clear();
+            }
+        }
+    }
+
+    @Override
+    public void dumpStatisticsAndClear() {
+        String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh,
+                mCountTextureRect, mCountFillRect, mCountDrawLine);
+        mCountDrawMesh = 0;
+        mCountTextureRect = 0;
+        mCountFillRect = 0;
+        mCountDrawLine = 0;
+        Log.d(TAG, line);
+    }
+
+    @Override
+    public void endRenderTarget() {
+        RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1);
+        RawTexture texture = getTargetTexture();
+        setRenderTarget(oldTexture, texture);
+        restore(); // restore matrix and alpha
+    }
+
+    @Override
+    public void beginRenderTarget(RawTexture texture) {
+        save(); // save matrix and alpha and blending
+        RawTexture oldTexture = getTargetTexture();
+        mTargetTextures.add(texture);
+        setRenderTarget(oldTexture, texture);
+    }
+
+    private RawTexture getTargetTexture() {
+        return mTargetTextures.get(mTargetTextures.size() - 1);
+    }
+
+    private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) {
+        if (oldTexture == null && texture != null) {
+            GLES20.glGenFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]);
+            checkError();
+        } else if (oldTexture != null && texture == null) {
+            GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+            checkError();
+            GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0);
+            checkError();
+        }
+
+        if (texture == null) {
+            setSize(mScreenWidth, mScreenHeight);
+        } else {
+            setSize(texture.getWidth(), texture.getHeight());
+
+            if (!texture.isLoaded()) {
+                texture.prepare(this);
+            }
+
+            GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0,
+                    texture.getTarget(), texture.getId(), 0);
+            checkError();
+
+            checkFramebufferStatus();
+        }
+    }
+
+    private static void checkFramebufferStatus() {
+        int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER);
+        if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) {
+            String msg = "";
+            switch (status) {
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
+                    msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
+                    break;
+                case GLES20.GL_FRAMEBUFFER_UNSUPPORTED:
+                    msg = "GL_FRAMEBUFFER_UNSUPPORTED";
+                    break;
+            }
+            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
+        }
+    }
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+    }
+
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        int width = texture.getTextureWidth();
+        int height = texture.getTextureHeight();
+        GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null);
+    }
+
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texImage2D(target, 0, bitmap, 0);
+    }
+
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+        int target = texture.getTarget();
+        GLES20.glBindTexture(target, texture.getId());
+        checkError();
+        GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type);
+    }
+
+    @Override
+    public int uploadBuffer(FloatBuffer buf) {
+        return uploadBuffer(buf, FLOAT_SIZE);
+    }
+
+    @Override
+    public int uploadBuffer(ByteBuffer buf) {
+        return uploadBuffer(buf, 1);
+    }
+
+    private int uploadBuffer(Buffer buffer, int elementSize) {
+        mGLId.glGenBuffers(1, mTempIntArray, 0);
+        checkError();
+        int bufferId = mTempIntArray[0];
+        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId);
+        checkError();
+        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer,
+                GLES20.GL_STATIC_DRAW);
+        checkError();
+        return bufferId;
+    }
+
+    public static void checkError() {
+        int error = GLES20.glGetError();
+        if (error != 0) {
+            Throwable t = new Throwable();
+            Log.e(TAG, "GL error: " + error, t);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void printMatrix(String message, float[] m, int offset) {
+        StringBuilder b = new StringBuilder(message);
+        for (int i = 0; i < MATRIX_SIZE; i++) {
+            b.append(' ');
+            if (i % 4 == 0) {
+                b.append('\n');
+            }
+            b.append(m[offset + i]);
+        }
+        Log.v(TAG, b.toString());
+    }
+
+    @Override
+    public void recoverFromLightCycle() {
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
+        checkError();
+    }
+
+    @Override
+    public void getBounds(Rect bounds, int x, int y, int width, int height) {
+        Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f);
+        Matrix.scaleM(mTempMatrix, 0, width, height, 1f);
+        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0);
+        Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4);
+        bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]);
+        bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]);
+        bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]);
+        bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]);
+        bounds.sort();
+    }
+
+    @Override
+    public GLId getGLId() {
+        return mGLId;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
new file mode 100644
index 0000000..6cd7149
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLES20IdImpl.java
@@ -0,0 +1,42 @@
+package com.android.gallery3d.glrenderer;
+
+import android.opengl.GLES20;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+public class GLES20IdImpl implements GLId {
+    private final int[] mTempIntArray = new int[1];
+
+    @Override
+    public int generateTexture() {
+        GLES20.glGenTextures(1, mTempIntArray, 0);
+        GLES20Canvas.checkError();
+        return mTempIntArray[0];
+    }
+
+    @Override
+    public void glGenBuffers(int n, int[] buffers, int offset) {
+        GLES20.glGenBuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+
+    @Override
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
+        GLES20.glDeleteTextures(n, textures, offset);
+        GLES20Canvas.checkError();
+    }
+
+
+    @Override
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
+        GLES20.glDeleteBuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+
+    @Override
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
+        GLES20.glDeleteFramebuffers(n, buffers, offset);
+        GLES20Canvas.checkError();
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLId.java b/src/com/android/gallery3d/glrenderer/GLId.java
new file mode 100644
index 0000000..3cec558
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLId.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11ExtensionPack;
+
+// This mimics corresponding GL functions.
+public interface GLId {
+    public int generateTexture();
+
+    public void glGenBuffers(int n, int[] buffers, int offset);
+
+    public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset);
+
+    public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset);
+
+    public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset);
+}
diff --git a/src/com/android/gallery3d/glrenderer/GLPaint.java b/src/com/android/gallery3d/glrenderer/GLPaint.java
new file mode 100644
index 0000000..16b2206
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/GLPaint.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import junit.framework.Assert;
+
+public class GLPaint {
+    private float mLineWidth = 1f;
+    private int mColor = 0;
+
+    public void setColor(int color) {
+        mColor = color;
+    }
+
+    public int getColor() {
+        return mColor;
+    }
+
+    public void setLineWidth(float width) {
+        Assert.assertTrue(width >= 0);
+        mLineWidth = width;
+    }
+
+    public float getLineWidth() {
+        return mLineWidth;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/MultiLineTexture.java b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java
new file mode 100644
index 0000000..82839f1
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.text.Layout;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+
+// MultiLineTexture is a texture shows the content of a specified String.
+//
+// To create a MultiLineTexture, use the newInstance() method and specify
+// the String, the font size, and the color.
+class MultiLineTexture extends CanvasTexture {
+    private final Layout mLayout;
+
+    private MultiLineTexture(Layout layout) {
+        super(layout.getWidth(), layout.getHeight());
+        mLayout = layout;
+    }
+
+    public static MultiLineTexture newInstance(
+            String text, int maxWidth, float textSize, int color,
+            Layout.Alignment alignment) {
+        TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
+        Layout layout = new StaticLayout(text, 0, text.length(), paint,
+                maxWidth, alignment, 1, 0, true, null, 0);
+
+        return new MultiLineTexture(layout);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, Bitmap backing) {
+        mLayout.draw(canvas);
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/NinePatchChunk.java b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java
new file mode 100644
index 0000000..9dc3266
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Rect;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
+// NinePatch chunk.
+class NinePatchChunk {
+
+    public static final int NO_COLOR = 0x00000001;
+    public static final int TRANSPARENT_COLOR = 0x00000000;
+
+    public Rect mPaddings = new Rect();
+
+    public int mDivX[];
+    public int mDivY[];
+    public int mColor[];
+
+    private static void readIntArray(int[] data, ByteBuffer buffer) {
+        for (int i = 0, n = data.length; i < n; ++i) {
+            data[i] = buffer.getInt();
+        }
+    }
+
+    private static void checkDivCount(int length) {
+        if (length == 0 || (length & 0x01) != 0) {
+            throw new RuntimeException("invalid nine-patch: " + length);
+        }
+    }
+
+    public static NinePatchChunk deserialize(byte[] data) {
+        ByteBuffer byteBuffer =
+                ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
+
+        byte wasSerialized = byteBuffer.get();
+        if (wasSerialized == 0) return null;
+
+        NinePatchChunk chunk = new NinePatchChunk();
+        chunk.mDivX = new int[byteBuffer.get()];
+        chunk.mDivY = new int[byteBuffer.get()];
+        chunk.mColor = new int[byteBuffer.get()];
+
+        checkDivCount(chunk.mDivX.length);
+        checkDivCount(chunk.mDivY.length);
+
+        // skip 8 bytes
+        byteBuffer.getInt();
+        byteBuffer.getInt();
+
+        chunk.mPaddings.left = byteBuffer.getInt();
+        chunk.mPaddings.right = byteBuffer.getInt();
+        chunk.mPaddings.top = byteBuffer.getInt();
+        chunk.mPaddings.bottom = byteBuffer.getInt();
+
+        // skip 4 bytes
+        byteBuffer.getInt();
+
+        readIntArray(chunk.mDivX, byteBuffer);
+        readIntArray(chunk.mDivY, byteBuffer);
+        readIntArray(chunk.mColor, byteBuffer);
+
+        return chunk;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/glrenderer/NinePatchTexture.java b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java
new file mode 100644
index 0000000..d0ddc46
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+
+import com.android.gallery3d.common.Utils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+// NinePatchTexture is a texture backed by a NinePatch resource.
+//
+// getPaddings() returns paddings specified in the NinePatch.
+// getNinePatchChunk() returns the layout data specified in the NinePatch.
+//
+public class NinePatchTexture extends ResourceTexture {
+    @SuppressWarnings("unused")
+    private static final String TAG = "NinePatchTexture";
+    private NinePatchChunk mChunk;
+    private SmallCache<NinePatchInstance> mInstanceCache
+            = new SmallCache<NinePatchInstance>();
+
+    public NinePatchTexture(Context context, int resId) {
+        super(context, resId);
+    }
+
+    @Override
+    protected Bitmap onGetBitmap() {
+        if (mBitmap != null) return mBitmap;
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        Bitmap bitmap = BitmapFactory.decodeResource(
+                mContext.getResources(), mResId, options);
+        mBitmap = bitmap;
+        setSize(bitmap.getWidth(), bitmap.getHeight());
+        byte[] chunkData = bitmap.getNinePatchChunk();
+        mChunk = chunkData == null
+                ? null
+                : NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
+        if (mChunk == null) {
+            throw new RuntimeException("invalid nine-patch image: " + mResId);
+        }
+        return bitmap;
+    }
+
+    public Rect getPaddings() {
+        // get the paddings from nine patch
+        if (mChunk == null) onGetBitmap();
+        return mChunk.mPaddings;
+    }
+
+    public NinePatchChunk getNinePatchChunk() {
+        if (mChunk == null) onGetBitmap();
+        return mChunk;
+    }
+
+    // This is a simple cache for a small number of things. Linear search
+    // is used because the cache is small. It also tries to remove less used
+    // item when the cache is full by moving the often-used items to the front.
+    private static class SmallCache<V> {
+        private static final int CACHE_SIZE = 16;
+        private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
+        private int[] mKey = new int[CACHE_SIZE];
+        private V[] mValue = (V[]) new Object[CACHE_SIZE];
+        private int mCount;  // number of items in this cache
+
+        // Puts a value into the cache. If the cache is full, also returns
+        // a less used item, otherwise returns null.
+        public V put(int key, V value) {
+            if (mCount == CACHE_SIZE) {
+                V old = mValue[CACHE_SIZE - 1];  // remove the last item
+                mKey[CACHE_SIZE - 1] = key;
+                mValue[CACHE_SIZE - 1] = value;
+                return old;
+            } else {
+                mKey[mCount] = key;
+                mValue[mCount] = value;
+                mCount++;
+                return null;
+            }
+        }
+
+        public V get(int key) {
+            for (int i = 0; i < mCount; i++) {
+                if (mKey[i] == key) {
+                    // Move the accessed item one position to the front, so it
+                    // will less likely to be removed when cache is full. Only
+                    // do this if the cache is starting to get full.
+                    if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
+                        int tmpKey = mKey[i];
+                        mKey[i] = mKey[i - 1];
+                        mKey[i - 1] = tmpKey;
+
+                        V tmpValue = mValue[i];
+                        mValue[i] = mValue[i - 1];
+                        mValue[i - 1] = tmpValue;
+                    }
+                    return mValue[i];
+                }
+            }
+            return null;
+        }
+
+        public void clear() {
+            for (int i = 0; i < mCount; i++) {
+                mValue[i] = null;  // make sure it's can be garbage-collected.
+            }
+            mCount = 0;
+        }
+
+        public int size() {
+            return mCount;
+        }
+
+        public V valueAt(int i) {
+            return mValue[i];
+        }
+    }
+
+    private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
+        int key = w;
+        key = (key << 16) | h;
+        NinePatchInstance instance = mInstanceCache.get(key);
+
+        if (instance == null) {
+            instance = new NinePatchInstance(this, w, h);
+            NinePatchInstance removed = mInstanceCache.put(key, instance);
+            if (removed != null) {
+                removed.recycle(canvas);
+            }
+        }
+
+        return instance;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
+        if (!isLoaded()) {
+            mInstanceCache.clear();
+        }
+
+        if (w != 0 && h != 0) {
+            findInstance(canvas, w, h).draw(canvas, this, x, y);
+        }
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        GLCanvas canvas = mCanvasRef;
+        if (canvas == null) return;
+        int n = mInstanceCache.size();
+        for (int i = 0; i < n; i++) {
+            NinePatchInstance instance = mInstanceCache.valueAt(i);
+            instance.recycle(canvas);
+        }
+        mInstanceCache.clear();
+    }
+}
+
+// This keeps data for a specialization of NinePatchTexture with the size
+// (width, height). We pre-compute the coordinates for efficiency.
+class NinePatchInstance {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "NinePatchInstance";
+
+    // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
+    private static final int VERTEX_BUFFER_SIZE = 16 * 2;
+
+    // We need 22 indices for a normal nine-patch image, plus 2 for each
+    // transparent region. Current there are at most 1 transparent region.
+    private static final int INDEX_BUFFER_SIZE = 22 + 2;
+
+    private FloatBuffer mXyBuffer;
+    private FloatBuffer mUvBuffer;
+    private ByteBuffer mIndexBuffer;
+
+    // Names for buffer names: xy, uv, index.
+    private int mXyBufferName = -1;
+    private int mUvBufferName;
+    private int mIndexBufferName;
+
+    private int mIdxCount;
+
+    public NinePatchInstance(NinePatchTexture tex, int width, int height) {
+        NinePatchChunk chunk = tex.getNinePatchChunk();
+
+        if (width <= 0 || height <= 0) {
+            throw new RuntimeException("invalid dimension");
+        }
+
+        // The code should be easily extended to handle the general cases by
+        // allocating more space for buffers. But let's just handle the only
+        // use case.
+        if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
+            throw new RuntimeException("unsupported nine patch");
+        }
+
+        float divX[] = new float[4];
+        float divY[] = new float[4];
+        float divU[] = new float[4];
+        float divV[] = new float[4];
+
+        int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
+        int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
+
+        prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
+    }
+
+    /**
+     * Stretches the texture according to the nine-patch rules. It will
+     * linearly distribute the strechy parts defined in the nine-patch chunk to
+     * the target area.
+     *
+     * <pre>
+     *                      source
+     *          /--------------^---------------\
+     *         u0    u1       u2  u3     u4   u5
+     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
+     *          |    div0    div1 div2   div3  |
+     *          |     |       /   /      /    /
+     *          |     |      /   /     /    /
+     *          |     |     /   /    /    /
+     *          |fffff|ssss|fff|sss|ffff| ---> x
+     *         x0    x1   x2  x3  x4   x5
+     *          \----------v------------/
+     *                  target
+     *
+     * f: fixed segment
+     * s: stretchy segment
+     * </pre>
+     *
+     * @param div the stretch parts defined in nine-patch chunk
+     * @param source the length of the texture
+     * @param target the length on the drawing plan
+     * @param u output, the positions of these dividers in the texture
+     *        coordinate
+     * @param x output, the corresponding position of these dividers on the
+     *        drawing plan
+     * @return the number of these dividers.
+     */
+    private static int stretch(
+            float x[], float u[], int div[], int source, int target) {
+        int textureSize = Utils.nextPowerOf2(source);
+        float textureBound = (float) source / textureSize;
+
+        float stretch = 0;
+        for (int i = 0, n = div.length; i < n; i += 2) {
+            stretch += div[i + 1] - div[i];
+        }
+
+        float remaining = target - source + stretch;
+
+        float lastX = 0;
+        float lastU = 0;
+
+        x[0] = 0;
+        u[0] = 0;
+        for (int i = 0, n = div.length; i < n; i += 2) {
+            // Make the stretchy segment a little smaller to prevent sampling
+            // on neighboring fixed segments.
+            // fixed segment
+            x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
+            u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
+
+            // stretchy segment
+            float partU = div[i + 1] - div[i];
+            float partX = remaining * partU / stretch;
+            remaining -= partX;
+            stretch -= partU;
+
+            lastX = x[i + 1] + partX;
+            lastU = div[i + 1];
+            x[i + 2] = lastX - 0.5f;
+            u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
+        }
+        // the last fixed segment
+        x[div.length + 1] = target;
+        u[div.length + 1] = textureBound;
+
+        // remove segments with length 0.
+        int last = 0;
+        for (int i = 1, n = div.length + 2; i < n; ++i) {
+            if ((x[i] - x[last]) < 1f) continue;
+            x[++last] = x[i];
+            u[last] = u[i];
+        }
+        return last + 1;
+    }
+
+    private void prepareVertexData(float x[], float y[], float u[], float v[],
+            int nx, int ny, int[] color) {
+        /*
+         * Given a 3x3 nine-patch image, the vertex order is defined as the
+         * following graph:
+         *
+         * (0) (1) (2) (3)
+         *  |  /|  /|  /|
+         *  | / | / | / |
+         * (4) (5) (6) (7)
+         *  | \ | \ | \ |
+         *  |  \|  \|  \|
+         * (8) (9) (A) (B)
+         *  |  /|  /|  /|
+         *  | / | / | / |
+         * (C) (D) (E) (F)
+         *
+         * And we draw the triangle strip in the following index order:
+         *
+         * index: 04152637B6A5948C9DAEBF
+         */
+        int pntCount = 0;
+        float xy[] = new float[VERTEX_BUFFER_SIZE];
+        float uv[] = new float[VERTEX_BUFFER_SIZE];
+        for (int j = 0; j < ny; ++j) {
+            for (int i = 0; i < nx; ++i) {
+                int xIndex = (pntCount++) << 1;
+                int yIndex = xIndex + 1;
+                xy[xIndex] = x[i];
+                xy[yIndex] = y[j];
+                uv[xIndex] = u[i];
+                uv[yIndex] = v[j];
+            }
+        }
+
+        int idxCount = 1;
+        boolean isForward = false;
+        byte index[] = new byte[INDEX_BUFFER_SIZE];
+        for (int row = 0; row < ny - 1; row++) {
+            --idxCount;
+            isForward = !isForward;
+
+            int start, end, inc;
+            if (isForward) {
+                start = 0;
+                end = nx;
+                inc = 1;
+            } else {
+                start = nx - 1;
+                end = -1;
+                inc = -1;
+            }
+
+            for (int col = start; col != end; col += inc) {
+                int k = row * nx + col;
+                if (col != start) {
+                    int colorIdx = row * (nx - 1) + col;
+                    if (isForward) colorIdx--;
+                    if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
+                        index[idxCount] = index[idxCount - 1];
+                        ++idxCount;
+                        index[idxCount++] = (byte) k;
+                    }
+                }
+
+                index[idxCount++] = (byte) k;
+                index[idxCount++] = (byte) (k + nx);
+            }
+        }
+
+        mIdxCount = idxCount;
+
+        int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
+        mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+        mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
+        mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
+
+        mXyBuffer.put(xy, 0, pntCount * 2).position(0);
+        mUvBuffer.put(uv, 0, pntCount * 2).position(0);
+        mIndexBuffer.put(index, 0, idxCount).position(0);
+    }
+
+    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
+        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+    }
+
+    private void prepareBuffers(GLCanvas canvas) {
+        mXyBufferName = canvas.uploadBuffer(mXyBuffer);
+        mUvBufferName = canvas.uploadBuffer(mUvBuffer);
+        mIndexBufferName = canvas.uploadBuffer(mIndexBuffer);
+
+        // These buffers are never used again.
+        mXyBuffer = null;
+        mUvBuffer = null;
+        mIndexBuffer = null;
+    }
+
+    public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
+        if (mXyBufferName == -1) {
+            prepareBuffers(canvas);
+        }
+        canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount);
+    }
+
+    public void recycle(GLCanvas canvas) {
+        if (mXyBuffer == null) {
+            canvas.deleteBuffer(mXyBufferName);
+            canvas.deleteBuffer(mUvBufferName);
+            canvas.deleteBuffer(mIndexBufferName);
+            mXyBufferName = -1;
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java
new file mode 100644
index 0000000..93f0fdf
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/RawTexture.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.util.Log;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class RawTexture extends BasicTexture {
+    private static final String TAG = "RawTexture";
+
+    private final boolean mOpaque;
+    private boolean mIsFlipped;
+
+    public RawTexture(int width, int height, boolean opaque) {
+        mOpaque = opaque;
+        setSize(width, height);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    @Override
+    public boolean isFlippedVertically() {
+        return mIsFlipped;
+    }
+
+    public void setIsFlippedVertically(boolean isFlipped) {
+        mIsFlipped = isFlipped;
+    }
+
+    protected void prepare(GLCanvas canvas) {
+        GLId glId = canvas.getGLId();
+        mId = glId.generateTexture();
+        canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
+        canvas.setTextureParameters(this);
+        mState = STATE_LOADED;
+        setAssociatedCanvas(canvas);
+    }
+
+    @Override
+    protected boolean onBind(GLCanvas canvas) {
+        if (isLoaded()) return true;
+        Log.w(TAG, "lost the content due to context change");
+        return false;
+    }
+
+    @Override
+     public void yield() {
+         // we cannot free the texture because we have no backup.
+     }
+
+    @Override
+    protected int getTarget() {
+        return GL11.GL_TEXTURE_2D;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/ResourceTexture.java b/src/com/android/gallery3d/glrenderer/ResourceTexture.java
new file mode 100644
index 0000000..eb8e8a5
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/ResourceTexture.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import junit.framework.Assert;
+
+// ResourceTexture is a texture whose Bitmap is decoded from a resource.
+// By default ResourceTexture is not opaque.
+public class ResourceTexture extends UploadedTexture {
+
+    protected final Context mContext;
+    protected final int mResId;
+
+    public ResourceTexture(Context context, int resId) {
+        Assert.assertNotNull(context);
+        mContext = context;
+        mResId = resId;
+        setOpaque(false);
+    }
+
+    @Override
+    protected Bitmap onGetBitmap() {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        return BitmapFactory.decodeResource(
+                mContext.getResources(), mResId, options);
+    }
+
+    @Override
+    protected void onFreeBitmap(Bitmap bitmap) {
+        if (!inFinalizer()) {
+            bitmap.recycle();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/StringTexture.java b/src/com/android/gallery3d/glrenderer/StringTexture.java
new file mode 100644
index 0000000..56ca297
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/StringTexture.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Typeface;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.util.FloatMath;
+
+// StringTexture is a texture shows the content of a specified String.
+//
+// To create a StringTexture, use the newInstance() method and specify
+// the String, the font size, and the color.
+public class StringTexture extends CanvasTexture {
+    private final String mText;
+    private final TextPaint mPaint;
+    private final FontMetricsInt mMetrics;
+
+    private StringTexture(String text, TextPaint paint,
+            FontMetricsInt metrics, int width, int height) {
+        super(width, height);
+        mText = text;
+        mPaint = paint;
+        mMetrics = metrics;
+    }
+
+    public static TextPaint getDefaultPaint(float textSize, int color) {
+        TextPaint paint = new TextPaint();
+        paint.setTextSize(textSize);
+        paint.setAntiAlias(true);
+        paint.setColor(color);
+        paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
+        return paint;
+    }
+
+    public static StringTexture newInstance(
+            String text, float textSize, int color) {
+        return newInstance(text, getDefaultPaint(textSize, color));
+    }
+
+    public static StringTexture newInstance(
+            String text, float textSize, int color,
+            float lengthLimit, boolean isBold) {
+        TextPaint paint = getDefaultPaint(textSize, color);
+        if (isBold) {
+            paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+        }
+        if (lengthLimit > 0) {
+            text = TextUtils.ellipsize(
+                    text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
+        }
+        return newInstance(text, paint);
+    }
+
+    private static StringTexture newInstance(String text, TextPaint paint) {
+        FontMetricsInt metrics = paint.getFontMetricsInt();
+        int width = (int) FloatMath.ceil(paint.measureText(text));
+        int height = metrics.bottom - metrics.top;
+        // The texture size needs to be at least 1x1.
+        if (width <= 0) width = 1;
+        if (height <= 0) height = 1;
+        return new StringTexture(text, paint, metrics, width, height);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas, Bitmap backing) {
+        canvas.translate(0, -mMetrics.ascent);
+        canvas.drawText(mText, 0, 0, mPaint);
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java
new file mode 100644
index 0000000..3dcae4a
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/Texture.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+
+// Texture is a rectangular image which can be drawn on GLCanvas.
+// The isOpaque() function gives a hint about whether the texture is opaque,
+// so the drawing can be done faster.
+//
+// This is the current texture hierarchy:
+//
+// Texture
+// -- ColorTexture
+// -- FadeInTexture
+// -- BasicTexture
+//    -- UploadedTexture
+//       -- BitmapTexture
+//       -- Tile
+//       -- ResourceTexture
+//          -- NinePatchTexture
+//       -- CanvasTexture
+//          -- StringTexture
+//
+public interface Texture {
+    public int getWidth();
+    public int getHeight();
+    public void draw(GLCanvas canvas, int x, int y);
+    public void draw(GLCanvas canvas, int x, int y, int w, int h);
+    public boolean isOpaque();
+}
diff --git a/src/com/android/gallery3d/glrenderer/TextureUploader.java b/src/com/android/gallery3d/glrenderer/TextureUploader.java
new file mode 100644
index 0000000..f17ab84
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/TextureUploader.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+    private static final int INIT_CAPACITY = 64;
+    private static final int QUOTA_PER_FRAME = 1;
+
+    private final ArrayDeque<UploadedTexture> mFgTextures =
+            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+    private final ArrayDeque<UploadedTexture> mBgTextures =
+            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+    private final GLRoot mGLRoot;
+    private volatile boolean mIsQueued = false;
+
+    public TextureUploader(GLRoot root) {
+        mGLRoot = root;
+    }
+
+    public synchronized void clear() {
+        while (!mFgTextures.isEmpty()) {
+            mFgTextures.pop().setIsUploading(false);
+        }
+        while (!mBgTextures.isEmpty()) {
+            mBgTextures.pop().setIsUploading(false);
+        }
+    }
+
+    // caller should hold synchronized on "this"
+    private void queueSelfIfNeed() {
+        if (mIsQueued) return;
+        mIsQueued = true;
+        mGLRoot.addOnGLIdleListener(this);
+    }
+
+    public synchronized void addBgTexture(UploadedTexture t) {
+        if (t.isContentValid()) return;
+        mBgTextures.addLast(t);
+        t.setIsUploading(true);
+        queueSelfIfNeed();
+    }
+
+    public synchronized void addFgTexture(UploadedTexture t) {
+        if (t.isContentValid()) return;
+        mFgTextures.addLast(t);
+        t.setIsUploading(true);
+        queueSelfIfNeed();
+    }
+
+    private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
+            int uploadQuota, boolean isBackground) {
+        while (uploadQuota > 0) {
+            UploadedTexture t;
+            synchronized (this) {
+                if (deque.isEmpty()) break;
+                t = deque.removeFirst();
+                t.setIsUploading(false);
+                if (t.isContentValid()) continue;
+
+                // this has to be protected by the synchronized block
+                // to prevent the inner bitmap get recycled
+                t.updateContent(canvas);
+            }
+
+            // It will took some more time for a texture to be drawn for
+            // the first time.
+            // Thus, when scrolling, if a new column appears on screen,
+            // it may cause a UI jank even these textures are uploaded.
+            if (isBackground) t.draw(canvas, 0, 0);
+            --uploadQuota;
+        }
+        return uploadQuota;
+    }
+
+    @Override
+    public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+        int uploadQuota = QUOTA_PER_FRAME;
+        uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+        if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+        upload(canvas, mBgTextures, uploadQuota, true);
+        synchronized (this) {
+            mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+            return mIsQueued;
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/TiledTexture.java b/src/com/android/gallery3d/glrenderer/TiledTexture.java
new file mode 100644
index 0000000..6ca1de0
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/TiledTexture.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.os.SystemClock;
+
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+
+// This class is similar to BitmapTexture, except the bitmap is
+// split into tiles. By doing so, we may increase the time required to
+// upload the whole bitmap but we reduce the time of uploading each tile
+// so it make the animation more smooth and prevents jank.
+public class TiledTexture implements Texture {
+    private static final int CONTENT_SIZE = 254;
+    private static final int BORDER_SIZE = 1;
+    private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
+    private static final int INIT_CAPACITY = 8;
+
+    // We are targeting at 60fps, so we have 16ms for each frame.
+    // In this 16ms, we use about 4~8 ms to upload tiles.
+    private static final long UPLOAD_TILE_LIMIT = 4; // ms
+
+    private static Tile sFreeTileHead = null;
+    private static final Object sFreeTileLock = new Object();
+
+    private static Bitmap sUploadBitmap;
+    private static Canvas sCanvas;
+    private static Paint sBitmapPaint;
+    private static Paint sPaint;
+
+    private int mUploadIndex = 0;
+
+    private final Tile[] mTiles;  // Can be modified in different threads.
+                                  // Should be protected by "synchronized."
+    private final int mWidth;
+    private final int mHeight;
+    private final RectF mSrcRect = new RectF();
+    private final RectF mDestRect = new RectF();
+
+    public static class Uploader implements OnGLIdleListener {
+        private final ArrayDeque<TiledTexture> mTextures =
+                new ArrayDeque<TiledTexture>(INIT_CAPACITY);
+
+        private final GLRoot mGlRoot;
+        private boolean mIsQueued = false;
+
+        public Uploader(GLRoot glRoot) {
+            mGlRoot = glRoot;
+        }
+
+        public synchronized void clear() {
+            mTextures.clear();
+        }
+
+        public synchronized void addTexture(TiledTexture t) {
+            if (t.isReady()) return;
+            mTextures.addLast(t);
+
+            if (mIsQueued) return;
+            mIsQueued = true;
+            mGlRoot.addOnGLIdleListener(this);
+        }
+
+        @Override
+        public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+            ArrayDeque<TiledTexture> deque = mTextures;
+            synchronized (this) {
+                long now = SystemClock.uptimeMillis();
+                long dueTime = now + UPLOAD_TILE_LIMIT;
+                while (now < dueTime && !deque.isEmpty()) {
+                    TiledTexture t = deque.peekFirst();
+                    if (t.uploadNextTile(canvas)) {
+                        deque.removeFirst();
+                        mGlRoot.requestRender();
+                    }
+                    now = SystemClock.uptimeMillis();
+                }
+                mIsQueued = !mTextures.isEmpty();
+
+                // return true to keep this listener in the queue
+                return mIsQueued;
+            }
+        }
+    }
+
+    private static class Tile extends UploadedTexture {
+        public int offsetX;
+        public int offsetY;
+        public Bitmap bitmap;
+        public Tile nextFreeTile;
+        public int contentWidth;
+        public int contentHeight;
+
+        @Override
+        public void setSize(int width, int height) {
+            contentWidth = width;
+            contentHeight = height;
+            mWidth = width + 2 * BORDER_SIZE;
+            mHeight = height + 2 * BORDER_SIZE;
+            mTextureWidth = TILE_SIZE;
+            mTextureHeight = TILE_SIZE;
+        }
+
+        @Override
+        protected Bitmap onGetBitmap() {
+            int x = BORDER_SIZE - offsetX;
+            int y = BORDER_SIZE - offsetY;
+            int r = bitmap.getWidth() + x;
+            int b = bitmap.getHeight() + y;
+            sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
+            bitmap = null;
+
+            // draw borders if need
+            if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
+            if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
+            if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
+            if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
+
+            return sUploadBitmap;
+        }
+
+        @Override
+        protected void onFreeBitmap(Bitmap bitmap) {
+            // do nothing
+        }
+    }
+
+    private static void freeTile(Tile tile) {
+        tile.invalidateContent();
+        tile.bitmap = null;
+        synchronized (sFreeTileLock) {
+            tile.nextFreeTile = sFreeTileHead;
+            sFreeTileHead = tile;
+        }
+    }
+
+    private static Tile obtainTile() {
+        synchronized (sFreeTileLock) {
+            Tile result = sFreeTileHead;
+            if (result == null) return new Tile();
+            sFreeTileHead = result.nextFreeTile;
+            result.nextFreeTile = null;
+            return result;
+        }
+    }
+
+    private boolean uploadNextTile(GLCanvas canvas) {
+        if (mUploadIndex == mTiles.length) return true;
+
+        synchronized (mTiles) {
+            Tile next = mTiles[mUploadIndex++];
+
+            // Make sure tile has not already been recycled by the time
+            // this is called (race condition in onGLIdle)
+            if (next.bitmap != null) {
+                boolean hasBeenLoad = next.isLoaded();
+                next.updateContent(canvas);
+
+                // It will take some time for a texture to be drawn for the first
+                // time. When scrolling, we need to draw several tiles on the screen
+                // at the same time. It may cause a UI jank even these textures has
+                // been uploaded.
+                if (!hasBeenLoad) next.draw(canvas, 0, 0);
+            }
+        }
+        return mUploadIndex == mTiles.length;
+    }
+
+    public TiledTexture(Bitmap bitmap) {
+        mWidth = bitmap.getWidth();
+        mHeight = bitmap.getHeight();
+        ArrayList<Tile> list = new ArrayList<Tile>();
+
+        for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
+            for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
+                Tile tile = obtainTile();
+                tile.offsetX = x;
+                tile.offsetY = y;
+                tile.bitmap = bitmap;
+                tile.setSize(
+                        Math.min(CONTENT_SIZE, mWidth - x),
+                        Math.min(CONTENT_SIZE, mHeight - y));
+                list.add(tile);
+            }
+        }
+        mTiles = list.toArray(new Tile[list.size()]);
+    }
+
+    public boolean isReady() {
+        return mUploadIndex == mTiles.length;
+    }
+
+    // Can be called in UI thread.
+    public void recycle() {
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                freeTile(mTiles[i]);
+            }
+        }
+    }
+
+    public static void freeResources() {
+        sUploadBitmap = null;
+        sCanvas = null;
+        sBitmapPaint = null;
+        sPaint = null;
+    }
+
+    public static void prepareResources() {
+        sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
+        sCanvas = new Canvas(sUploadBitmap);
+        sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+        sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+        sPaint = new Paint();
+        sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
+        sPaint.setColor(Color.TRANSPARENT);
+    }
+
+    // We want to draw the "source" on the "target".
+    // This method is to find the "output" rectangle which is
+    // the corresponding area of the "src".
+    //                                   (x,y)  target
+    // (x0,y0)  source                     +---------------+
+    //    +----------+                     |               |
+    //    | src      |                     | output        |
+    //    | +--+     |    linear map       | +----+        |
+    //    | +--+     |    ---------->      | |    |        |
+    //    |          | by (scaleX, scaleY) | +----+        |
+    //    +----------+                     |               |
+    //      Texture                        +---------------+
+    //                                          Canvas
+    private static void mapRect(RectF output,
+            RectF src, float x0, float y0, float x, float y, float scaleX,
+            float scaleY) {
+        output.set(x + (src.left - x0) * scaleX,
+                y + (src.top - y0) * scaleY,
+                x + (src.right - x0) * scaleX,
+                y + (src.bottom - y0) * scaleY);
+    }
+
+    // Draws a mixed color of this texture and a specified color onto the
+    // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
+    public void drawMixed(GLCanvas canvas, int color, float ratio,
+            int x, int y, int width, int height) {
+        RectF src = mSrcRect;
+        RectF dest = mDestRect;
+        float scaleX = (float) width / mWidth;
+        float scaleY = (float) height / mHeight;
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
+            }
+        }
+    }
+
+    // Draws the texture on to the specified rectangle.
+    @Override
+    public void draw(GLCanvas canvas, int x, int y, int width, int height) {
+        RectF src = mSrcRect;
+        RectF dest = mDestRect;
+        float scaleX = (float) width / mWidth;
+        float scaleY = (float) height / mHeight;
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawTexture(t, mSrcRect, mDestRect);
+            }
+        }
+    }
+
+    // Draws a sub region of this texture on to the specified rectangle.
+    public void draw(GLCanvas canvas, RectF source, RectF target) {
+        RectF src = mSrcRect;
+        RectF dest = mDestRect;
+        float x0 = source.left;
+        float y0 = source.top;
+        float x = target.left;
+        float y = target.top;
+        float scaleX = target.width() / source.width();
+        float scaleY = target.height() / source.height();
+
+        synchronized (mTiles) {
+            for (int i = 0, n = mTiles.length; i < n; ++i) {
+                Tile t = mTiles[i];
+                src.set(0, 0, t.contentWidth, t.contentHeight);
+                src.offset(t.offsetX, t.offsetY);
+                if (!src.intersect(source)) continue;
+                mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
+                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
+                canvas.drawTexture(t, src, dest);
+            }
+        }
+    }
+
+    @Override
+    public int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    public void draw(GLCanvas canvas, int x, int y) {
+        draw(canvas, x, y, mWidth, mHeight);
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return false;
+    }
+}
diff --git a/src/com/android/gallery3d/glrenderer/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
new file mode 100644
index 0000000..f41a979
--- /dev/null
+++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.opengl.GLUtils;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+
+import javax.microedition.khronos.opengles.GL11;
+
+// UploadedTextures use a Bitmap for the content of the texture.
+//
+// Subclasses should implement onGetBitmap() to provide the Bitmap and
+// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
+// is not needed anymore.
+//
+// isContentValid() is meaningful only when the isLoaded() returns true.
+// It means whether the content needs to be updated.
+//
+// The user of this class should call recycle() when the texture is not
+// needed anymore.
+//
+// By default an UploadedTexture is opaque (so it can be drawn faster without
+// blending). The user or subclass can override it using setOpaque().
+public abstract class UploadedTexture extends BasicTexture {
+
+    // To prevent keeping allocation the borders, we store those used borders here.
+    // Since the length will be power of two, it won't use too much memory.
+    private static HashMap<BorderKey, Bitmap> sBorderLines =
+            new HashMap<BorderKey, Bitmap>();
+    private static BorderKey sBorderKey = new BorderKey();
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "Texture";
+    private boolean mContentValid = true;
+
+    // indicate this textures is being uploaded in background
+    private boolean mIsUploading = false;
+    private boolean mOpaque = true;
+    private boolean mThrottled = false;
+    private static int sUploadedCount;
+    private static final int UPLOAD_LIMIT = 100;
+
+    protected Bitmap mBitmap;
+    private int mBorder;
+
+    protected UploadedTexture() {
+        this(false);
+    }
+
+    protected UploadedTexture(boolean hasBorder) {
+        super(null, 0, STATE_UNLOADED);
+        if (hasBorder) {
+            setBorder(true);
+            mBorder = 1;
+        }
+    }
+
+    protected void setIsUploading(boolean uploading) {
+        mIsUploading = uploading;
+    }
+
+    public boolean isUploading() {
+        return mIsUploading;
+    }
+
+    private static class BorderKey implements Cloneable {
+        public boolean vertical;
+        public Config config;
+        public int length;
+
+        @Override
+        public int hashCode() {
+            int x = config.hashCode() ^ length;
+            return vertical ? x : -x;
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (!(object instanceof BorderKey)) return false;
+            BorderKey o = (BorderKey) object;
+            return vertical == o.vertical
+                    && config == o.config && length == o.length;
+        }
+
+        @Override
+        public BorderKey clone() {
+            try {
+                return (BorderKey) super.clone();
+            } catch (CloneNotSupportedException e) {
+                throw new AssertionError(e);
+            }
+        }
+    }
+
+    protected void setThrottled(boolean throttled) {
+        mThrottled = throttled;
+    }
+
+    private static Bitmap getBorderLine(
+            boolean vertical, Config config, int length) {
+        BorderKey key = sBorderKey;
+        key.vertical = vertical;
+        key.config = config;
+        key.length = length;
+        Bitmap bitmap = sBorderLines.get(key);
+        if (bitmap == null) {
+            bitmap = vertical
+                    ? Bitmap.createBitmap(1, length, config)
+                    : Bitmap.createBitmap(length, 1, config);
+            sBorderLines.put(key.clone(), bitmap);
+        }
+        return bitmap;
+    }
+
+    private Bitmap getBitmap() {
+        if (mBitmap == null) {
+            mBitmap = onGetBitmap();
+            int w = mBitmap.getWidth() + mBorder * 2;
+            int h = mBitmap.getHeight() + mBorder * 2;
+            if (mWidth == UNSPECIFIED) {
+                setSize(w, h);
+            }
+        }
+        return mBitmap;
+    }
+
+    private void freeBitmap() {
+        Assert.assertTrue(mBitmap != null);
+        onFreeBitmap(mBitmap);
+        mBitmap = null;
+    }
+
+    @Override
+    public int getWidth() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mWidth;
+    }
+
+    @Override
+    public int getHeight() {
+        if (mWidth == UNSPECIFIED) getBitmap();
+        return mHeight;
+    }
+
+    protected abstract Bitmap onGetBitmap();
+
+    protected abstract void onFreeBitmap(Bitmap bitmap);
+
+    protected void invalidateContent() {
+        if (mBitmap != null) freeBitmap();
+        mContentValid = false;
+        mWidth = UNSPECIFIED;
+        mHeight = UNSPECIFIED;
+    }
+
+    /**
+     * Whether the content on GPU is valid.
+     */
+    public boolean isContentValid() {
+        return isLoaded() && mContentValid;
+    }
+
+    /**
+     * Updates the content on GPU's memory.
+     * @param canvas
+     */
+    public void updateContent(GLCanvas canvas) {
+        if (!isLoaded()) {
+            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
+                return;
+            }
+            uploadToCanvas(canvas);
+        } else if (!mContentValid) {
+            Bitmap bitmap = getBitmap();
+            int format = GLUtils.getInternalFormat(bitmap);
+            int type = GLUtils.getType(bitmap);
+            canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+            freeBitmap();
+            mContentValid = true;
+        }
+    }
+
+    public static void resetUploadLimit() {
+        sUploadedCount = 0;
+    }
+
+    public static boolean uploadLimitReached() {
+        return sUploadedCount > UPLOAD_LIMIT;
+    }
+
+    private void uploadToCanvas(GLCanvas canvas) {
+
+        Bitmap bitmap = getBitmap();
+        if (bitmap != null) {
+            try {
+                int bWidth = bitmap.getWidth();
+                int bHeight = bitmap.getHeight();
+                int width = bWidth + mBorder * 2;
+                int height = bHeight + mBorder * 2;
+                int texWidth = getTextureWidth();
+                int texHeight = getTextureHeight();
+
+                Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
+
+                // Upload the bitmap to a new texture.
+                mId = canvas.getGLId().generateTexture();
+                canvas.setTextureParameters(this);
+
+                if (bWidth == texWidth && bHeight == texHeight) {
+                    canvas.initializeTexture(this, bitmap);
+                } else {
+                    int format = GLUtils.getInternalFormat(bitmap);
+                    int type = GLUtils.getType(bitmap);
+                    Config config = bitmap.getConfig();
+
+                    canvas.initializeTextureSize(this, format, type);
+                    canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type);
+
+                    if (mBorder > 0) {
+                        // Left border
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        canvas.texSubImage2D(this, 0, 0, line, format, type);
+
+                        // Top border
+                        line = getBorderLine(false, config, texWidth);
+                        canvas.texSubImage2D(this, 0, 0, line, format, type);
+                    }
+
+                    // Right border
+                    if (mBorder + bWidth < texWidth) {
+                        Bitmap line = getBorderLine(true, config, texHeight);
+                        canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type);
+                    }
+
+                    // Bottom border
+                    if (mBorder + bHeight < texHeight) {
+                        Bitmap line = getBorderLine(false, config, texWidth);
+                        canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type);
+                    }
+                }
+            } finally {
+                freeBitmap();
+            }
+            // Update texture state.
+            setAssociatedCanvas(canvas);
+            mState = STATE_LOADED;
+            mContentValid = true;
+        } else {
+            mState = STATE_ERROR;
+            throw new RuntimeException("Texture load fail, no bitmap");
+        }
+    }
+
+    @Override
+    protected boolean onBind(GLCanvas canvas) {
+        updateContent(canvas);
+        return isContentValid();
+    }
+
+    @Override
+    protected int getTarget() {
+        return GL11.GL_TEXTURE_2D;
+    }
+
+    public void setOpaque(boolean isOpaque) {
+        mOpaque = isOpaque;
+    }
+
+    @Override
+    public boolean isOpaque() {
+        return mOpaque;
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        if (mBitmap != null) freeBitmap();
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ImportTask.java b/src/com/android/gallery3d/ingest/ImportTask.java
new file mode 100644
index 0000000..d850bb8
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ImportTask.java
@@ -0,0 +1,94 @@
+/*
+ * 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.gallery3d.ingest;
+
+import android.content.Context;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.Environment;
+import android.os.PowerManager;
+
+import com.android.gallery3d.util.GalleryUtils;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ImportTask implements Runnable {
+
+    public interface Listener {
+        void onImportProgress(int visitedCount, int totalCount, String pathIfSuccessful);
+
+        void onImportFinish(Collection<MtpObjectInfo> objectsNotImported, int visitedCount);
+    }
+
+    static private final String WAKELOCK_LABEL = "MTP Import Task";
+
+    private Listener mListener;
+    private String mDestAlbumName;
+    private Collection<MtpObjectInfo> mObjectsToImport;
+    private MtpDevice mDevice;
+    private PowerManager.WakeLock mWakeLock;
+
+    public ImportTask(MtpDevice device, Collection<MtpObjectInfo> objectsToImport,
+            String destAlbumName, Context context) {
+        mDestAlbumName = destAlbumName;
+        mObjectsToImport = objectsToImport;
+        mDevice = device;
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, WAKELOCK_LABEL);
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void run() {
+        mWakeLock.acquire();
+        try {
+            List<MtpObjectInfo> objectsNotImported = new LinkedList<MtpObjectInfo>();
+            int visited = 0;
+            int total = mObjectsToImport.size();
+            File dest = new File(Environment.getExternalStorageDirectory(), mDestAlbumName);
+            dest.mkdirs();
+            for (MtpObjectInfo object : mObjectsToImport) {
+                visited++;
+                String importedPath = null;
+                if (GalleryUtils.hasSpaceForSize(object.getCompressedSize())) {
+                    importedPath = new File(dest, object.getName()).getAbsolutePath();
+                    if (!mDevice.importFile(object.getObjectHandle(), importedPath)) {
+                        importedPath = null;
+                    }
+                }
+                if (importedPath == null) {
+                    objectsNotImported.add(object);
+                }
+                if (mListener != null) {
+                    mListener.onImportProgress(visited, total, importedPath);
+                }
+            }
+            if (mListener != null) {
+                mListener.onImportFinish(objectsNotImported, visited);
+            }
+        } finally {
+            mListener = null;
+            mWakeLock.release();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/IngestActivity.java b/src/com/android/gallery3d/ingest/IngestActivity.java
new file mode 100644
index 0000000..ffc4b50
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/IngestActivity.java
@@ -0,0 +1,549 @@
+/*
+ * 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.gallery3d.ingest;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.res.Configuration;
+import android.database.DataSetObserver;
+import android.mtp.MtpObjectInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.support.v4.view.ViewPager;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.adapter.CheckBroker;
+import com.android.gallery3d.ingest.adapter.MtpAdapter;
+import com.android.gallery3d.ingest.adapter.MtpPagerAdapter;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+import com.android.gallery3d.ingest.ui.DateTileView;
+import com.android.gallery3d.ingest.ui.IngestGridView;
+import com.android.gallery3d.ingest.ui.IngestGridView.OnClearChoicesListener;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+
+public class IngestActivity extends Activity implements
+        MtpDeviceIndex.ProgressListener, ImportTask.Listener {
+
+    private IngestService mHelperService;
+    private boolean mActive = false;
+    private IngestGridView mGridView;
+    private MtpAdapter mAdapter;
+    private Handler mHandler;
+    private ProgressDialog mProgressDialog;
+    private ActionMode mActiveActionMode;
+
+    private View mWarningView;
+    private TextView mWarningText;
+    private int mLastCheckedPosition = 0;
+
+    private ViewPager mFullscreenPager;
+    private MtpPagerAdapter mPagerAdapter;
+    private boolean mFullscreenPagerVisible = false;
+
+    private MenuItem mMenuSwitcherItem;
+    private MenuItem mActionMenuSwitcherItem;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        doBindHelperService();
+
+        setContentView(R.layout.ingest_activity_item_list);
+        mGridView = (IngestGridView) findViewById(R.id.ingest_gridview);
+        mAdapter = new MtpAdapter(this);
+        mAdapter.registerDataSetObserver(mMasterObserver);
+        mGridView.setAdapter(mAdapter);
+        mGridView.setMultiChoiceModeListener(mMultiChoiceModeListener);
+        mGridView.setOnItemClickListener(mOnItemClickListener);
+        mGridView.setOnClearChoicesListener(mPositionMappingCheckBroker);
+
+        mFullscreenPager = (ViewPager) findViewById(R.id.ingest_view_pager);
+
+        mHandler = new ItemListHandler(this);
+
+        MtpBitmapFetch.configureForContext(this);
+    }
+
+    private OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
+        @Override
+        public void onItemClick(AdapterView<?> adapterView, View itemView, int position, long arg3) {
+            mLastCheckedPosition = position;
+            mGridView.setItemChecked(position, !mGridView.getCheckedItemPositions().get(position));
+        }
+    };
+
+    private MultiChoiceModeListener mMultiChoiceModeListener = new MultiChoiceModeListener() {
+        private boolean mIgnoreItemCheckedStateChanges = false;
+
+        private void updateSelectedTitle(ActionMode mode) {
+            int count = mGridView.getCheckedItemCount();
+            mode.setTitle(getResources().getQuantityString(
+                    R.plurals.number_of_items_selected, count, count));
+        }
+
+        @Override
+        public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+                boolean checked) {
+            if (mIgnoreItemCheckedStateChanges) return;
+            if (mAdapter.itemAtPositionIsBucket(position)) {
+                SparseBooleanArray checkedItems = mGridView.getCheckedItemPositions();
+                mIgnoreItemCheckedStateChanges = true;
+                mGridView.setItemChecked(position, false);
+
+                // Takes advantage of the fact that SectionIndexer imposes the
+                // need to clamp to the valid range
+                int nextSectionStart = mAdapter.getPositionForSection(
+                        mAdapter.getSectionForPosition(position) + 1);
+                if (nextSectionStart == position)
+                    nextSectionStart = mAdapter.getCount();
+
+                boolean rangeValue = false; // Value we want to set all of the bucket items to
+
+                // Determine if all the items in the bucket are currently checked, so that we
+                // can uncheck them, otherwise we will check all items in the bucket.
+                for (int i = position + 1; i < nextSectionStart; i++) {
+                    if (checkedItems.get(i) == false) {
+                        rangeValue = true;
+                        break;
+                    }
+                }
+
+                // Set all items in the bucket to the desired state
+                for (int i = position + 1; i < nextSectionStart; i++) {
+                    if (checkedItems.get(i) != rangeValue)
+                        mGridView.setItemChecked(i, rangeValue);
+                }
+
+                mPositionMappingCheckBroker.onBulkCheckedChange();
+                mIgnoreItemCheckedStateChanges = false;
+            } else {
+                mPositionMappingCheckBroker.onCheckedChange(position, checked);
+            }
+            mLastCheckedPosition = position;
+            updateSelectedTitle(mode);
+        }
+
+        @Override
+        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+            return onOptionsItemSelected(item);
+        }
+
+        @Override
+        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            MenuInflater inflater = mode.getMenuInflater();
+            inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
+            updateSelectedTitle(mode);
+            mActiveActionMode = mode;
+            mActionMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
+            setSwitcherMenuState(mActionMenuSwitcherItem, mFullscreenPagerVisible);
+            return true;
+        }
+
+        @Override
+        public void onDestroyActionMode(ActionMode mode) {
+            mActiveActionMode = null;
+            mActionMenuSwitcherItem = null;
+            mHandler.sendEmptyMessage(ItemListHandler.MSG_BULK_CHECKED_CHANGE);
+        }
+
+        @Override
+        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+            updateSelectedTitle(mode);
+            return false;
+        }
+    };
+
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.import_items:
+                if (mActiveActionMode != null) {
+                    mHelperService.importSelectedItems(
+                            mGridView.getCheckedItemPositions(),
+                            mAdapter);
+                    mActiveActionMode.finish();
+                }
+                return true;
+            case R.id.ingest_switch_view:
+                setFullscreenPagerVisibility(!mFullscreenPagerVisible);
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        MenuInflater inflater = getMenuInflater();
+        inflater.inflate(R.menu.ingest_menu_item_list_selection, menu);
+        mMenuSwitcherItem = menu.findItem(R.id.ingest_switch_view);
+        menu.findItem(R.id.import_items).setVisible(false);
+        setSwitcherMenuState(mMenuSwitcherItem, mFullscreenPagerVisible);
+        return true;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        doUnbindHelperService();
+    }
+
+    @Override
+    protected void onResume() {
+        DateTileView.refreshLocale();
+        mActive = true;
+        if (mHelperService != null) mHelperService.setClientActivity(this);
+        updateWarningView();
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        if (mHelperService != null) mHelperService.setClientActivity(null);
+        mActive = false;
+        cleanupProgressDialog();
+        super.onPause();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        MtpBitmapFetch.configureForContext(this);
+    }
+
+    private void showWarningView(int textResId) {
+        if (mWarningView == null) {
+            mWarningView = findViewById(R.id.ingest_warning_view);
+            mWarningText =
+                    (TextView)mWarningView.findViewById(R.id.ingest_warning_view_text);
+        }
+        mWarningText.setText(textResId);
+        mWarningView.setVisibility(View.VISIBLE);
+        setFullscreenPagerVisibility(false);
+        mGridView.setVisibility(View.GONE);
+    }
+
+    private void hideWarningView() {
+        if (mWarningView != null) {
+            mWarningView.setVisibility(View.GONE);
+            setFullscreenPagerVisibility(false);
+        }
+    }
+
+    private PositionMappingCheckBroker mPositionMappingCheckBroker = new PositionMappingCheckBroker();
+
+    private class PositionMappingCheckBroker extends CheckBroker
+        implements OnClearChoicesListener {
+        private int mLastMappingPager = -1;
+        private int mLastMappingGrid = -1;
+
+        private int mapPagerToGridPosition(int position) {
+            if (position != mLastMappingPager) {
+               mLastMappingPager = position;
+               mLastMappingGrid = mAdapter.translatePositionWithoutLabels(position);
+            }
+            return mLastMappingGrid;
+        }
+
+        private int mapGridToPagerPosition(int position) {
+            if (position != mLastMappingGrid) {
+                mLastMappingGrid = position;
+                mLastMappingPager = mPagerAdapter.translatePositionWithLabels(position);
+            }
+            return mLastMappingPager;
+        }
+
+        @Override
+        public void setItemChecked(int position, boolean checked) {
+            mGridView.setItemChecked(mapPagerToGridPosition(position), checked);
+        }
+
+        @Override
+        public void onCheckedChange(int position, boolean checked) {
+            if (mPagerAdapter != null) {
+                super.onCheckedChange(mapGridToPagerPosition(position), checked);
+            }
+        }
+
+        @Override
+        public boolean isItemChecked(int position) {
+            return mGridView.getCheckedItemPositions().get(mapPagerToGridPosition(position));
+        }
+
+        @Override
+        public void onClearChoices() {
+            onBulkCheckedChange();
+        }
+    };
+
+    private DataSetObserver mMasterObserver = new DataSetObserver() {
+        @Override
+        public void onChanged() {
+            if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
+        }
+
+        @Override
+        public void onInvalidated() {
+            if (mPagerAdapter != null) mPagerAdapter.notifyDataSetChanged();
+        }
+    };
+
+    private int pickFullscreenStartingPosition() {
+        int firstVisiblePosition = mGridView.getFirstVisiblePosition();
+        if (mLastCheckedPosition <= firstVisiblePosition
+                || mLastCheckedPosition > mGridView.getLastVisiblePosition()) {
+            return firstVisiblePosition;
+        } else {
+            return mLastCheckedPosition;
+        }
+    }
+
+    private void setSwitcherMenuState(MenuItem menuItem, boolean inFullscreenMode) {
+        if (menuItem == null) return;
+        if (!inFullscreenMode) {
+            menuItem.setIcon(android.R.drawable.ic_menu_zoom);
+            menuItem.setTitle(R.string.switch_photo_fullscreen);
+        } else {
+            menuItem.setIcon(android.R.drawable.ic_dialog_dialer);
+            menuItem.setTitle(R.string.switch_photo_grid);
+        }
+    }
+
+    private void setFullscreenPagerVisibility(boolean visible) {
+        mFullscreenPagerVisible = visible;
+        if (visible) {
+            if (mPagerAdapter == null) {
+                mPagerAdapter = new MtpPagerAdapter(this, mPositionMappingCheckBroker);
+                mPagerAdapter.setMtpDeviceIndex(mAdapter.getMtpDeviceIndex());
+            }
+            mFullscreenPager.setAdapter(mPagerAdapter);
+            mFullscreenPager.setCurrentItem(mPagerAdapter.translatePositionWithLabels(
+                    pickFullscreenStartingPosition()), false);
+        } else if (mPagerAdapter != null) {
+            mGridView.setSelection(mAdapter.translatePositionWithoutLabels(
+                    mFullscreenPager.getCurrentItem()));
+            mFullscreenPager.setAdapter(null);
+        }
+        mGridView.setVisibility(visible ? View.INVISIBLE : View.VISIBLE);
+        mFullscreenPager.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        if (mActionMenuSwitcherItem != null) {
+            setSwitcherMenuState(mActionMenuSwitcherItem, visible);
+        }
+        setSwitcherMenuState(mMenuSwitcherItem, visible);
+    }
+
+    private void updateWarningView() {
+        if (!mAdapter.deviceConnected()) {
+            showWarningView(R.string.ingest_no_device);
+        } else if (mAdapter.indexReady() && mAdapter.getCount() == 0) {
+            showWarningView(R.string.ingest_empty_device);
+        } else {
+            hideWarningView();
+        }
+    }
+
+    private void UiThreadNotifyIndexChanged() {
+        mAdapter.notifyDataSetChanged();
+        if (mActiveActionMode != null) {
+            mActiveActionMode.finish();
+            mActiveActionMode = null;
+        }
+        updateWarningView();
+    }
+
+    protected void notifyIndexChanged() {
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+    }
+
+    private static class ProgressState {
+        String message;
+        String title;
+        int current;
+        int max;
+
+        public void reset() {
+            title = null;
+            message = null;
+            current = 0;
+            max = 0;
+        }
+    }
+
+    private ProgressState mProgressState = new ProgressState();
+
+    @Override
+    public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
+        // Not guaranteed to be called on the UI thread
+        mProgressState.reset();
+        mProgressState.max = 0;
+        mProgressState.message = getResources().getQuantityString(
+                R.plurals.ingest_number_of_items_scanned, numVisited, numVisited);
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+    }
+
+    @Override
+    public void onSorting() {
+        // Not guaranteed to be called on the UI thread
+        mProgressState.reset();
+        mProgressState.max = 0;
+        mProgressState.message = getResources().getString(R.string.ingest_sorting);
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+    }
+
+    @Override
+    public void onIndexFinish() {
+        // Not guaranteed to be called on the UI thread
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_NOTIFY_CHANGED);
+    }
+
+    @Override
+    public void onImportProgress(final int visitedCount, final int totalCount,
+            String pathIfSuccessful) {
+        // Not guaranteed to be called on the UI thread
+        mProgressState.reset();
+        mProgressState.max = totalCount;
+        mProgressState.current = visitedCount;
+        mProgressState.title = getResources().getString(R.string.ingest_importing);
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE);
+    }
+
+    @Override
+    public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
+            int numVisited) {
+        // Not guaranteed to be called on the UI thread
+        mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE);
+        // TODO: maybe show an extra dialog listing the ones that failed
+        // importing, if any?
+    }
+
+    private ProgressDialog getProgressDialog() {
+        if (mProgressDialog == null || !mProgressDialog.isShowing()) {
+            mProgressDialog = new ProgressDialog(this);
+            mProgressDialog.setCancelable(false);
+        }
+        return mProgressDialog;
+    }
+
+    private void updateProgressDialog() {
+        ProgressDialog dialog = getProgressDialog();
+        boolean indeterminate = (mProgressState.max == 0);
+        dialog.setIndeterminate(indeterminate);
+        dialog.setProgressStyle(indeterminate ? ProgressDialog.STYLE_SPINNER
+                : ProgressDialog.STYLE_HORIZONTAL);
+        if (mProgressState.title != null) {
+            dialog.setTitle(mProgressState.title);
+        }
+        if (mProgressState.message != null) {
+            dialog.setMessage(mProgressState.message);
+        }
+        if (!indeterminate) {
+            dialog.setProgress(mProgressState.current);
+            dialog.setMax(mProgressState.max);
+        }
+        if (!dialog.isShowing()) {
+            dialog.show();
+        }
+    }
+
+    private void cleanupProgressDialog() {
+        if (mProgressDialog != null) {
+            mProgressDialog.hide();
+            mProgressDialog = null;
+        }
+    }
+
+    // This is static and uses a WeakReference in order to avoid leaking the Activity
+    private static class ItemListHandler extends Handler {
+        public static final int MSG_PROGRESS_UPDATE = 0;
+        public static final int MSG_PROGRESS_HIDE = 1;
+        public static final int MSG_NOTIFY_CHANGED = 2;
+        public static final int MSG_BULK_CHECKED_CHANGE = 3;
+
+        WeakReference<IngestActivity> mParentReference;
+
+        public ItemListHandler(IngestActivity parent) {
+            super();
+            mParentReference = new WeakReference<IngestActivity>(parent);
+        }
+
+        public void handleMessage(Message message) {
+            IngestActivity parent = mParentReference.get();
+            if (parent == null || !parent.mActive)
+                return;
+            switch (message.what) {
+                case MSG_PROGRESS_HIDE:
+                    parent.cleanupProgressDialog();
+                    break;
+                case MSG_PROGRESS_UPDATE:
+                    parent.updateProgressDialog();
+                    break;
+                case MSG_NOTIFY_CHANGED:
+                    parent.UiThreadNotifyIndexChanged();
+                    break;
+                case MSG_BULK_CHECKED_CHANGE:
+                    parent.mPositionMappingCheckBroker.onBulkCheckedChange();
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+    private ServiceConnection mHelperServiceConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mHelperService = ((IngestService.LocalBinder) service).getService();
+            mHelperService.setClientActivity(IngestActivity.this);
+            MtpDeviceIndex index = mHelperService.getIndex();
+            mAdapter.setMtpDeviceIndex(index);
+            if (mPagerAdapter != null) mPagerAdapter.setMtpDeviceIndex(index);
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mHelperService = null;
+        }
+    };
+
+    private void doBindHelperService() {
+        bindService(new Intent(getApplicationContext(), IngestService.class),
+                mHelperServiceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private void doUnbindHelperService() {
+        if (mHelperService != null) {
+            mHelperService.setClientActivity(null);
+            unbindService(mHelperServiceConnection);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/IngestService.java b/src/com/android/gallery3d/ingest/IngestService.java
new file mode 100644
index 0000000..0ce3ab6
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/IngestService.java
@@ -0,0 +1,320 @@
+/*
+ * 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.gallery3d.ingest;
+
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.mtp.MtpDevice;
+import android.mtp.MtpDeviceInfo;
+import android.mtp.MtpObjectInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.support.v4.app.NotificationCompat;
+import android.util.SparseBooleanArray;
+import android.widget.Adapter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.NotificationIds;
+import com.android.gallery3d.data.MtpClient;
+import com.android.gallery3d.util.BucketNames;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class IngestService extends Service implements ImportTask.Listener,
+        MtpDeviceIndex.ProgressListener, MtpClient.Listener {
+
+    public class LocalBinder extends Binder {
+        IngestService getService() {
+            return IngestService.this;
+        }
+    }
+
+    private static final int PROGRESS_UPDATE_INTERVAL_MS = 180;
+
+    private static MtpClient sClient;
+
+    private final IBinder mBinder = new LocalBinder();
+    private ScannerClient mScannerClient;
+    private MtpDevice mDevice;
+    private String mDevicePrettyName;
+    private MtpDeviceIndex mIndex;
+    private IngestActivity mClientActivity;
+    private boolean mRedeliverImportFinish = false;
+    private int mRedeliverImportFinishCount = 0;
+    private Collection<MtpObjectInfo> mRedeliverObjectsNotImported;
+    private boolean mRedeliverNotifyIndexChanged = false;
+    private boolean mRedeliverIndexFinish = false;
+    private NotificationManager mNotificationManager;
+    private NotificationCompat.Builder mNotificationBuilder;
+    private long mLastProgressIndexTime = 0;
+    private boolean mNeedRelaunchNotification = false;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mScannerClient = new ScannerClient(this);
+        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+        mNotificationBuilder = new NotificationCompat.Builder(this);
+        mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync) // TODO drawable
+                .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, IngestActivity.class), 0));
+        mIndex = MtpDeviceIndex.getInstance();
+        mIndex.setProgressListener(this);
+
+        if (sClient == null) {
+            sClient = new MtpClient(getApplicationContext());
+        }
+        List<MtpDevice> devices = sClient.getDeviceList();
+        if (devices.size() > 0) {
+            setDevice(devices.get(0));
+        }
+        sClient.addListener(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        sClient.removeListener(this);
+        mIndex.unsetProgressListener(this);
+        super.onDestroy();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private void setDevice(MtpDevice device) {
+        if (mDevice == device) return;
+        mRedeliverImportFinish = false;
+        mRedeliverObjectsNotImported = null;
+        mRedeliverNotifyIndexChanged = false;
+        mRedeliverIndexFinish = false;
+        mDevice = device;
+        mIndex.setDevice(mDevice);
+        if (mDevice != null) {
+            MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
+            if (deviceInfo == null) {
+                setDevice(null);
+                return;
+            } else {
+                mDevicePrettyName = deviceInfo.getModel();
+                mNotificationBuilder.setContentTitle(mDevicePrettyName);
+                new Thread(mIndex.getIndexRunnable()).start();
+            }
+        } else {
+            mDevicePrettyName = null;
+        }
+        if (mClientActivity != null) {
+            mClientActivity.notifyIndexChanged();
+        } else {
+            mRedeliverNotifyIndexChanged = true;
+        }
+    }
+
+    protected MtpDeviceIndex getIndex() {
+        return mIndex;
+    }
+
+    protected void setClientActivity(IngestActivity activity) {
+        if (mClientActivity == activity) return;
+        mClientActivity = activity;
+        if (mClientActivity == null) {
+            if (mNeedRelaunchNotification) {
+                mNotificationBuilder.setProgress(0, 0, false)
+                    .setContentText(getResources().getText(R.string.ingest_scanning_done));
+                mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
+                    mNotificationBuilder.build());
+            }
+            return;
+        }
+        mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_IMPORTING);
+        mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
+        if (mRedeliverImportFinish) {
+            mClientActivity.onImportFinish(mRedeliverObjectsNotImported,
+                    mRedeliverImportFinishCount);
+            mRedeliverImportFinish = false;
+            mRedeliverObjectsNotImported = null;
+        }
+        if (mRedeliverNotifyIndexChanged) {
+            mClientActivity.notifyIndexChanged();
+            mRedeliverNotifyIndexChanged = false;
+        }
+        if (mRedeliverIndexFinish) {
+            mClientActivity.onIndexFinish();
+            mRedeliverIndexFinish = false;
+        }
+    }
+
+    protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
+        List<MtpObjectInfo> importHandles = new ArrayList<MtpObjectInfo>();
+        for (int i = 0; i < selected.size(); i++) {
+            if (selected.valueAt(i)) {
+                Object item = adapter.getItem(selected.keyAt(i));
+                if (item instanceof MtpObjectInfo) {
+                    importHandles.add(((MtpObjectInfo) item));
+                }
+            }
+        }
+        ImportTask task = new ImportTask(mDevice, importHandles, BucketNames.IMPORTED, this);
+        task.setListener(this);
+        mNotificationBuilder.setProgress(0, 0, true)
+            .setContentText(getResources().getText(R.string.ingest_importing));
+        startForeground(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
+                    mNotificationBuilder.build());
+        new Thread(task).start();
+    }
+
+    @Override
+    public void deviceAdded(MtpDevice device) {
+        if (mDevice == null) {
+            setDevice(device);
+            UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER,
+                    "DeviceConnected", null);
+        }
+    }
+
+    @Override
+    public void deviceRemoved(MtpDevice device) {
+        if (device == mDevice) {
+            setDevice(null);
+            mNeedRelaunchNotification = false;
+            mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
+        }
+    }
+
+    @Override
+    public void onImportProgress(int visitedCount, int totalCount,
+            String pathIfSuccessful) {
+        if (pathIfSuccessful != null) {
+            mScannerClient.scanPath(pathIfSuccessful);
+        }
+        mNeedRelaunchNotification = false;
+        if (mClientActivity != null) {
+            mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful);
+        }
+        mNotificationBuilder.setProgress(totalCount, visitedCount, false)
+            .setContentText(getResources().getText(R.string.ingest_importing));
+        mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
+                mNotificationBuilder.build());
+    }
+
+    @Override
+    public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported,
+            int visitedCount) {
+        stopForeground(true);
+        mNeedRelaunchNotification = true;
+        if (mClientActivity != null) {
+            mClientActivity.onImportFinish(objectsNotImported, visitedCount);
+        } else {
+            mRedeliverImportFinish = true;
+            mRedeliverObjectsNotImported = objectsNotImported;
+            mRedeliverImportFinishCount = visitedCount;
+            mNotificationBuilder.setProgress(0, 0, false)
+                .setContentText(getResources().getText(R.string.import_complete));
+            mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
+                    mNotificationBuilder.build());
+        }
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER,
+                "ImportFinished", null, visitedCount);
+    }
+
+    @Override
+    public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
+        mNeedRelaunchNotification = false;
+        if (mClientActivity != null) {
+            mClientActivity.onObjectIndexed(object, numVisited);
+        } else {
+            // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds
+            long currentTime = SystemClock.uptimeMillis();
+            if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) {
+                mLastProgressIndexTime = currentTime;
+                mNotificationBuilder.setProgress(0, numVisited, true)
+                        .setContentText(getResources().getText(R.string.ingest_scanning));
+                mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
+                        mNotificationBuilder.build());
+            }
+        }
+    }
+
+    @Override
+    public void onSorting() {
+        if (mClientActivity != null) mClientActivity.onSorting();
+    }
+
+    @Override
+    public void onIndexFinish() {
+        mNeedRelaunchNotification = true;
+        if (mClientActivity != null) {
+            mClientActivity.onIndexFinish();
+        } else {
+            mNotificationBuilder.setProgress(0, 0, false)
+                .setContentText(getResources().getText(R.string.ingest_scanning_done));
+            mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
+                    mNotificationBuilder.build());
+            mRedeliverIndexFinish = true;
+        }
+    }
+
+    // Copied from old Gallery3d code
+    private static final class ScannerClient implements MediaScannerConnectionClient {
+        ArrayList<String> mPaths = new ArrayList<String>();
+        MediaScannerConnection mScannerConnection;
+        boolean mConnected;
+        Object mLock = new Object();
+
+        public ScannerClient(Context context) {
+            mScannerConnection = new MediaScannerConnection(context, this);
+        }
+
+        public void scanPath(String path) {
+            synchronized (mLock) {
+                if (mConnected) {
+                    mScannerConnection.scanFile(path, null);
+                } else {
+                    mPaths.add(path);
+                    mScannerConnection.connect();
+                }
+            }
+        }
+
+        @Override
+        public void onMediaScannerConnected() {
+            synchronized (mLock) {
+                mConnected = true;
+                if (!mPaths.isEmpty()) {
+                    for (String path : mPaths) {
+                        mScannerConnection.scanFile(path, null);
+                    }
+                    mPaths.clear();
+                }
+            }
+        }
+
+        @Override
+        public void onScanCompleted(String path, Uri uri) {
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
new file mode 100644
index 0000000..e873dd1
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java
@@ -0,0 +1,575 @@
+/*
+ * 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.gallery3d.ingest;
+
+import android.mtp.MtpConstants;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+/**
+ * MTP objects in the index are organized into "buckets," or groupings.
+ * At present, these buckets are based on the date an item was created.
+ *
+ * When the index is created, the buckets are sorted in their natural
+ * order, and the items within the buckets sorted by the date they are taken.
+ *
+ * The index enables the access of items and bucket labels as one unified list.
+ * For example, let's say we have the following data in the index:
+ *    [Bucket A]: [photo 1], [photo 2]
+ *    [Bucket B]: [photo 3]
+ *
+ * Then the items can be thought of as being organized as a 5 element list:
+ *   [Bucket A], [photo 1], [photo 2], [Bucket B], [photo 3]
+ *
+ * The data can also be accessed in descending order, in which case the list
+ * would be a bit different from simply reversing the ascending list, since the
+ * bucket labels need to always be at the beginning:
+ *   [Bucket B], [photo 3], [Bucket A], [photo 2], [photo 1]
+ *
+ * The index enables all the following operations in constant time, both for
+ * ascending and descending views of the data:
+ *   - get/getAscending/getDescending: get an item at a specified list position
+ *   - size: get the total number of items (bucket labels and MTP objects)
+ *   - getFirstPositionForBucketNumber
+ *   - getBucketNumberForPosition
+ *   - isFirstInBucket
+ *
+ * See the comments in buildLookupIndex for implementation notes.
+ */
+public class MtpDeviceIndex {
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((mDevice == null) ? 0 : mDevice.getDeviceId());
+        result = prime * result + mGeneration;
+        return result;
+    }
+
+    public interface ProgressListener {
+        public void onObjectIndexed(MtpObjectInfo object, int numVisited);
+
+        public void onSorting();
+
+        public void onIndexFinish();
+    }
+
+    public enum SortOrder {
+        Ascending, Descending
+    }
+
+    private MtpDevice mDevice;
+    private int[] mUnifiedLookupIndex;
+    private MtpObjectInfo[] mMtpObjects;
+    private DateBucket[] mBuckets;
+    private int mGeneration = 0;
+
+    public enum Progress {
+        Uninitialized, Initialized, Pending, Started, Sorting, Finished
+    }
+
+    private Progress mProgress = Progress.Uninitialized;
+    private ProgressListener mProgressListener;
+
+    private static final MtpDeviceIndex sInstance = new MtpDeviceIndex();
+    private static final MtpObjectTimestampComparator sMtpObjectComparator =
+            new MtpObjectTimestampComparator();
+
+    public static MtpDeviceIndex getInstance() {
+        return sInstance;
+    }
+
+    private MtpDeviceIndex() {
+    }
+
+    synchronized public MtpDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Sets the MtpDevice that should be indexed and initializes state, but does
+     * not kick off the actual indexing task, which is instead done by using
+     * {@link #getIndexRunnable()}
+     *
+     * @param device The MtpDevice that should be indexed
+     */
+    synchronized public void setDevice(MtpDevice device) {
+        if (device == mDevice) return;
+        mDevice = device;
+        resetState();
+    }
+
+    /**
+     * Provides a Runnable for the indexing task assuming the state has already
+     * been correctly initialized (by calling {@link #setDevice(MtpDevice)}) and
+     * has not already been run.
+     *
+     * @return Runnable for the main indexing task
+     */
+    synchronized public Runnable getIndexRunnable() {
+        if (mProgress != Progress.Initialized) return null;
+        mProgress = Progress.Pending;
+        return new IndexRunnable(mDevice);
+    }
+
+    synchronized public boolean indexReady() {
+        return mProgress == Progress.Finished;
+    }
+
+    synchronized public Progress getProgress() {
+        return mProgress;
+    }
+
+    /**
+     * @param listener Listener to change to
+     * @return Progress at the time the listener was added (useful for
+     *         configuring initial UI state)
+     */
+    synchronized public Progress setProgressListener(ProgressListener listener) {
+        mProgressListener = listener;
+        return mProgress;
+    }
+
+    /**
+     * Make the listener null if it matches the argument
+     *
+     * @param listener Listener to unset, if currently registered
+     */
+    synchronized public void unsetProgressListener(ProgressListener listener) {
+        if (mProgressListener == listener)
+            mProgressListener = null;
+    }
+
+    /**
+     * @return The total number of elements in the index (labels and items)
+     */
+    public int size() {
+        return mProgress == Progress.Finished ? mUnifiedLookupIndex.length : 0;
+    }
+
+    /**
+     * @param position Index of item to fetch, where 0 is the first item in the
+     *            specified order
+     * @param order
+     * @return the bucket label or MtpObjectInfo at the specified position and
+     *         order
+     */
+    public Object get(int position, SortOrder order) {
+        if (mProgress != Progress.Finished) return null;
+        if(order == SortOrder.Ascending) {
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
+            if (bucket.unifiedStartIndex == position) {
+                return bucket.bucket;
+            } else {
+                return mMtpObjects[bucket.itemsStartIndex + position - 1
+                                   - bucket.unifiedStartIndex];
+            }
+        } else {
+            int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
+            if (bucket.unifiedEndIndex == zeroIndex) {
+                return bucket.bucket;
+            } else {
+                return mMtpObjects[bucket.itemsStartIndex + zeroIndex
+                                   - bucket.unifiedStartIndex];
+            }
+        }
+    }
+
+    /**
+     * @param position Index of item to fetch from a view of the data that doesn't
+     *            include labels and is in the specified order
+     * @return position-th item in specified order, when not including labels
+     */
+    public MtpObjectInfo getWithoutLabels(int position, SortOrder order) {
+        if (mProgress != Progress.Finished) return null;
+        if (order == SortOrder.Ascending) {
+            return mMtpObjects[position];
+        } else {
+            return mMtpObjects[mMtpObjects.length - 1 - position];
+        }
+    }
+
+    /**
+     * Although this is O(log(number of buckets)), and thus should not be used
+     * in hotspots, even if the attached device has items for every day for
+     * a five-year timeframe, it would still only take 11 iterations at most,
+     * so shouldn't be a huge issue.
+     * @param position Index of item to map from a view of the data that doesn't
+     *            include labels and is in the specified order
+     * @param order
+     * @return position in a view of the data that does include labels
+     */
+    public int getPositionFromPositionWithoutLabels(int position, SortOrder order) {
+        if (mProgress != Progress.Finished) return -1;
+        if (order == SortOrder.Descending) {
+            position = mMtpObjects.length - 1 - position;
+        }
+        int bucketNumber = 0;
+        int iMin = 0;
+        int iMax = mBuckets.length - 1;
+        while (iMax >= iMin) {
+            int iMid = (iMax + iMin) / 2;
+            if (mBuckets[iMid].itemsStartIndex + mBuckets[iMid].numItems <= position) {
+                iMin = iMid + 1;
+            } else if (mBuckets[iMid].itemsStartIndex > position) {
+                iMax = iMid - 1;
+            } else {
+                bucketNumber = iMid;
+                break;
+            }
+        }
+        int mappedPos = mBuckets[bucketNumber].unifiedStartIndex
+                + position - mBuckets[bucketNumber].itemsStartIndex;
+        if (order == SortOrder.Descending) {
+            mappedPos = mUnifiedLookupIndex.length - 1 - mappedPos;
+        }
+        return mappedPos;
+    }
+
+    public int getPositionWithoutLabelsFromPosition(int position, SortOrder order) {
+        if (mProgress != Progress.Finished) return -1;
+        if(order == SortOrder.Ascending) {
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[position]];
+            if (bucket.unifiedStartIndex == position) position++;
+            return bucket.itemsStartIndex + position - 1 - bucket.unifiedStartIndex;
+        } else {
+            int zeroIndex = mUnifiedLookupIndex.length - 1 - position;
+            DateBucket bucket = mBuckets[mUnifiedLookupIndex[zeroIndex]];
+            if (bucket.unifiedEndIndex == zeroIndex) zeroIndex--;
+            return mMtpObjects.length - 1 - bucket.itemsStartIndex
+                    - zeroIndex + bucket.unifiedStartIndex;
+        }
+    }
+
+    /**
+     * @return The number of MTP items in the index (without labels)
+     */
+    public int sizeWithoutLabels() {
+        return mProgress == Progress.Finished ? mMtpObjects.length : 0;
+    }
+
+    public int getFirstPositionForBucketNumber(int bucketNumber, SortOrder order) {
+        if (order == SortOrder.Ascending) {
+            return mBuckets[bucketNumber].unifiedStartIndex;
+        } else {
+            return mUnifiedLookupIndex.length - mBuckets[mBuckets.length - 1 - bucketNumber].unifiedEndIndex - 1;
+        }
+    }
+
+    public int getBucketNumberForPosition(int position, SortOrder order) {
+        if (order == SortOrder.Ascending) {
+            return mUnifiedLookupIndex[position];
+        } else {
+            return mBuckets.length - 1 - mUnifiedLookupIndex[mUnifiedLookupIndex.length - 1 - position];
+        }
+    }
+
+    public boolean isFirstInBucket(int position, SortOrder order) {
+        if (order == SortOrder.Ascending) {
+            return mBuckets[mUnifiedLookupIndex[position]].unifiedStartIndex == position;
+        } else {
+            position = mUnifiedLookupIndex.length - 1 - position;
+            return mBuckets[mUnifiedLookupIndex[position]].unifiedEndIndex == position;
+        }
+    }
+
+    private Object[] mCachedReverseBuckets;
+
+    public Object[] getBuckets(SortOrder order) {
+        if (mBuckets == null) return null;
+        if (order == SortOrder.Ascending) {
+            return mBuckets;
+        } else {
+            if (mCachedReverseBuckets == null) {
+                computeReversedBuckets();
+            }
+            return mCachedReverseBuckets;
+        }
+    }
+
+    /*
+     * See the comments for buildLookupIndex for notes on the specific fields of
+     * this class.
+     */
+    private class DateBucket implements Comparable<DateBucket> {
+        SimpleDate bucket;
+        List<MtpObjectInfo> tempElementsList = new ArrayList<MtpObjectInfo>();
+        int unifiedStartIndex;
+        int unifiedEndIndex;
+        int itemsStartIndex;
+        int numItems;
+
+        public DateBucket(SimpleDate bucket) {
+            this.bucket = bucket;
+        }
+
+        public DateBucket(SimpleDate bucket, MtpObjectInfo firstElement) {
+            this(bucket);
+            tempElementsList.add(firstElement);
+        }
+
+        void sortElements(Comparator<MtpObjectInfo> comparator) {
+            Collections.sort(tempElementsList, comparator);
+        }
+
+        @Override
+        public String toString() {
+            return bucket.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return bucket.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (!(obj instanceof DateBucket)) return false;
+            DateBucket other = (DateBucket) obj;
+            if (bucket == null) {
+                if (other.bucket != null) return false;
+            } else if (!bucket.equals(other.bucket)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int compareTo(DateBucket another) {
+            return this.bucket.compareTo(another.bucket);
+        }
+    }
+
+    /**
+     * Comparator to sort MtpObjectInfo objects by date created.
+     */
+    private static class MtpObjectTimestampComparator implements Comparator<MtpObjectInfo> {
+        @Override
+        public int compare(MtpObjectInfo o1, MtpObjectInfo o2) {
+            long diff = o1.getDateCreated() - o2.getDateCreated();
+            if (diff < 0) {
+                return -1;
+            } else if (diff == 0) {
+                return 0;
+            } else {
+                return 1;
+            }
+        }
+    }
+
+    private void resetState() {
+        mGeneration++;
+        mUnifiedLookupIndex = null;
+        mMtpObjects = null;
+        mBuckets = null;
+        mCachedReverseBuckets = null;
+        mProgress = (mDevice == null) ? Progress.Uninitialized : Progress.Initialized;
+    }
+
+
+    private class IndexRunnable implements Runnable {
+        private int[] mUnifiedLookupIndex;
+        private MtpObjectInfo[] mMtpObjects;
+        private DateBucket[] mBuckets;
+        private Map<SimpleDate, DateBucket> mBucketsTemp;
+        private MtpDevice mDevice;
+        private int mNumObjects = 0;
+
+        private class IndexingException extends Exception {};
+
+        public IndexRunnable(MtpDevice device) {
+            mDevice = device;
+        }
+
+        /*
+         * Implementation note: this is the way the index supports a lot of its operations in
+         * constant time and respecting the need to have bucket names always come before items
+         * in that bucket when accessing the list sequentially, both in ascending and descending
+         * orders.
+         *
+         * Let's say the data we have in the index is the following:
+         *  [Bucket A]: [photo 1], [photo 2]
+         *  [Bucket B]: [photo 3]
+         *
+         *  In this case, the lookup index array would be
+         *  [0, 0, 0, 1, 1]
+         *
+         *  Now, whether we access the list in ascending or descending order, we know which bucket
+         *  to look in (0 corresponds to A and 1 to B), and can return the bucket label as the first
+         *  item in a bucket as needed. The individual IndexBUckets have a startIndex and endIndex
+         *  that correspond to indices in this lookup index array, allowing us to calculate the
+         *  offset of the specific item we want from within a specific bucket.
+         */
+        private void buildLookupIndex() {
+            int numBuckets = mBuckets.length;
+            mUnifiedLookupIndex = new int[mNumObjects + numBuckets];
+            int currentUnifiedIndexEntry = 0;
+            int nextUnifiedEntry;
+
+            mMtpObjects = new MtpObjectInfo[mNumObjects];
+            int currentItemsEntry = 0;
+            for (int i = 0; i < numBuckets; i++) {
+                DateBucket bucket = mBuckets[i];
+                nextUnifiedEntry = currentUnifiedIndexEntry + bucket.tempElementsList.size() + 1;
+                Arrays.fill(mUnifiedLookupIndex, currentUnifiedIndexEntry, nextUnifiedEntry, i);
+                bucket.unifiedStartIndex = currentUnifiedIndexEntry;
+                bucket.unifiedEndIndex = nextUnifiedEntry - 1;
+                currentUnifiedIndexEntry = nextUnifiedEntry;
+
+                bucket.itemsStartIndex = currentItemsEntry;
+                bucket.numItems = bucket.tempElementsList.size();
+                for (int j = 0; j < bucket.numItems; j++) {
+                    mMtpObjects[currentItemsEntry] = bucket.tempElementsList.get(j);
+                    currentItemsEntry++;
+                }
+                bucket.tempElementsList = null;
+            }
+        }
+
+        private void copyResults() {
+            MtpDeviceIndex.this.mUnifiedLookupIndex = mUnifiedLookupIndex;
+            MtpDeviceIndex.this.mMtpObjects = mMtpObjects;
+            MtpDeviceIndex.this.mBuckets = mBuckets;
+            mUnifiedLookupIndex = null;
+            mMtpObjects = null;
+            mBuckets = null;
+        }
+
+        @Override
+        public void run() {
+            try {
+                indexDevice();
+            } catch (IndexingException e) {
+                synchronized (MtpDeviceIndex.this) {
+                    resetState();
+                    if (mProgressListener != null) {
+                        mProgressListener.onIndexFinish();
+                    }
+                }
+            }
+        }
+
+        private void indexDevice() throws IndexingException {
+            synchronized (MtpDeviceIndex.this) {
+                mProgress = Progress.Started;
+            }
+            mBucketsTemp = new HashMap<SimpleDate, DateBucket>();
+            for (int storageId : mDevice.getStorageIds()) {
+                if (mDevice != getDevice()) throw new IndexingException();
+                Stack<Integer> pendingDirectories = new Stack<Integer>();
+                pendingDirectories.add(0xFFFFFFFF); // start at the root of the device
+                while (!pendingDirectories.isEmpty()) {
+                    if (mDevice != getDevice()) throw new IndexingException();
+                    int dirHandle = pendingDirectories.pop();
+                    for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) {
+                        MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle);
+                        if (objectInfo == null) throw new IndexingException();
+                        switch (objectInfo.getFormat()) {
+                            case MtpConstants.FORMAT_JFIF:
+                            case MtpConstants.FORMAT_EXIF_JPEG:
+                                addObject(objectInfo);
+                                break;
+                            case MtpConstants.FORMAT_ASSOCIATION:
+                                pendingDirectories.add(objectHandle);
+                                break;
+                        }
+                    }
+                }
+            }
+            Collection<DateBucket> values = mBucketsTemp.values();
+            mBucketsTemp = null;
+            mBuckets = values.toArray(new DateBucket[values.size()]);
+            values = null;
+            synchronized (MtpDeviceIndex.this) {
+                mProgress = Progress.Sorting;
+                if (mProgressListener != null) {
+                    mProgressListener.onSorting();
+                }
+            }
+            sortAll();
+            buildLookupIndex();
+            synchronized (MtpDeviceIndex.this) {
+                if (mDevice != getDevice()) throw new IndexingException();
+                copyResults();
+
+                /*
+                 * In order for getBuckets to operate in constant time for descending
+                 * order, we must precompute a reversed array of the buckets, mainly
+                 * because the android.widget.SectionIndexer interface which adapters
+                 * that call getBuckets implement depends on section numbers to be
+                 * ascending relative to the scroll position, so we must have this for
+                 * descending order or the scrollbar goes crazy.
+                 */
+                computeReversedBuckets();
+
+                mProgress = Progress.Finished;
+                if (mProgressListener != null) {
+                    mProgressListener.onIndexFinish();
+                }
+            }
+        }
+
+        private SimpleDate mDateInstance = new SimpleDate();
+
+        private void addObject(MtpObjectInfo objectInfo) {
+            mNumObjects++;
+            mDateInstance.setTimestamp(objectInfo.getDateCreated());
+            DateBucket bucket = mBucketsTemp.get(mDateInstance);
+            if (bucket == null) {
+                bucket = new DateBucket(mDateInstance, objectInfo);
+                mBucketsTemp.put(mDateInstance, bucket);
+                mDateInstance = new SimpleDate(); // only create new date
+                                                  // objects when they are used
+                return;
+            } else {
+                bucket.tempElementsList.add(objectInfo);
+            }
+            if (mProgressListener != null) {
+                mProgressListener.onObjectIndexed(objectInfo, mNumObjects);
+            }
+        }
+
+        private void sortAll() {
+            Arrays.sort(mBuckets);
+            for (DateBucket bucket : mBuckets) {
+                bucket.sortElements(sMtpObjectComparator);
+            }
+        }
+
+    }
+
+    private void computeReversedBuckets() {
+        mCachedReverseBuckets = new Object[mBuckets.length];
+        for (int i = 0; i < mCachedReverseBuckets.length; i++) {
+            mCachedReverseBuckets[i] = mBuckets[mBuckets.length - 1 - i];
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/SimpleDate.java b/src/com/android/gallery3d/ingest/SimpleDate.java
new file mode 100644
index 0000000..05db2cd
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/SimpleDate.java
@@ -0,0 +1,114 @@
+/*
+ * 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.gallery3d.ingest;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+
+/**
+ * Represents a date (year, month, day)
+ */
+public class SimpleDate implements Comparable<SimpleDate> {
+    public int month; // MM
+    public int day; // DD
+    public int year; // YYYY
+    private long timestamp;
+    private String mCachedStringRepresentation;
+
+    public SimpleDate() {
+    }
+
+    public SimpleDate(long timestamp) {
+        setTimestamp(timestamp);
+    }
+
+    private static Calendar sCalendarInstance = Calendar.getInstance();
+
+    public void setTimestamp(long timestamp) {
+        synchronized (sCalendarInstance) {
+            // TODO find a more efficient way to convert a timestamp to a date?
+            sCalendarInstance.setTimeInMillis(timestamp);
+            this.day = sCalendarInstance.get(Calendar.DATE);
+            this.month = sCalendarInstance.get(Calendar.MONTH);
+            this.year = sCalendarInstance.get(Calendar.YEAR);
+            this.timestamp = timestamp;
+            mCachedStringRepresentation = DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + day;
+        result = prime * result + month;
+        result = prime * result + year;
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (!(obj instanceof SimpleDate))
+            return false;
+        SimpleDate other = (SimpleDate) obj;
+        if (year != other.year)
+            return false;
+        if (month != other.month)
+            return false;
+        if (day != other.day)
+            return false;
+        return true;
+    }
+
+    @Override
+    public int compareTo(SimpleDate other) {
+        int yearDiff = this.year - other.getYear();
+        if (yearDiff != 0)
+            return yearDiff;
+        else {
+            int monthDiff = this.month - other.getMonth();
+            if (monthDiff != 0)
+                return monthDiff;
+            else
+                return this.day - other.getDay();
+        }
+    }
+
+    public int getDay() {
+        return day;
+    }
+
+    public int getMonth() {
+        return month;
+    }
+
+    public int getYear() {
+        return year;
+    }
+
+    @Override
+    public String toString() {
+        if (mCachedStringRepresentation == null) {
+            mCachedStringRepresentation = DateFormat.getDateInstance(DateFormat.SHORT).format(timestamp);
+        }
+        return mCachedStringRepresentation;
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/adapter/CheckBroker.java b/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
new file mode 100644
index 0000000..6783f23
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/adapter/CheckBroker.java
@@ -0,0 +1,56 @@
+/*
+ * 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.gallery3d.ingest.adapter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+public abstract class CheckBroker {
+    private Collection<OnCheckedChangedListener> mListeners =
+            new ArrayList<OnCheckedChangedListener>();
+
+    public interface OnCheckedChangedListener {
+        public void onCheckedChanged(int position, boolean isChecked);
+        public void onBulkCheckedChanged();
+    }
+
+    public abstract void setItemChecked(int position, boolean checked);
+
+    public void onCheckedChange(int position, boolean checked) {
+        if (isItemChecked(position) != checked) {
+            for (OnCheckedChangedListener l : mListeners) {
+                l.onCheckedChanged(position, checked);
+            }
+        }
+    }
+
+    public void onBulkCheckedChange() {
+        for (OnCheckedChangedListener l : mListeners) {
+            l.onBulkCheckedChanged();
+        }
+    }
+
+    public abstract boolean isItemChecked(int position);
+
+    public void registerOnCheckedChangeListener(OnCheckedChangedListener l) {
+        mListeners.add(l);
+    }
+
+    public void unregisterOnCheckedChangeListener(OnCheckedChangedListener l) {
+        mListeners.remove(l);
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
new file mode 100644
index 0000000..e8dd69f
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/adapter/MtpAdapter.java
@@ -0,0 +1,192 @@
+/*
+ * 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.gallery3d.ingest.adapter;
+
+import android.app.Activity;
+import android.content.Context;
+import android.mtp.MtpObjectInfo;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.SectionIndexer;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.MtpDeviceIndex;
+import com.android.gallery3d.ingest.MtpDeviceIndex.SortOrder;
+import com.android.gallery3d.ingest.SimpleDate;
+import com.android.gallery3d.ingest.ui.DateTileView;
+import com.android.gallery3d.ingest.ui.MtpThumbnailTileView;
+
+public class MtpAdapter extends BaseAdapter implements SectionIndexer {
+    public static final int ITEM_TYPE_MEDIA = 0;
+    public static final int ITEM_TYPE_BUCKET = 1;
+
+    private Context mContext;
+    private MtpDeviceIndex mModel;
+    private SortOrder mSortOrder = SortOrder.Descending;
+    private LayoutInflater mInflater;
+    private int mGeneration = 0;
+
+    public MtpAdapter(Activity context) {
+        super();
+        mContext = context;
+        mInflater = LayoutInflater.from(context);
+    }
+
+    public void setMtpDeviceIndex(MtpDeviceIndex index) {
+        mModel = index;
+        notifyDataSetChanged();
+    }
+
+    public MtpDeviceIndex getMtpDeviceIndex() {
+        return mModel;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        mGeneration++;
+        super.notifyDataSetChanged();
+    }
+
+    @Override
+    public void notifyDataSetInvalidated() {
+        mGeneration++;
+        super.notifyDataSetInvalidated();
+    }
+
+    public boolean deviceConnected() {
+        return (mModel != null) && (mModel.getDevice() != null);
+    }
+
+    public boolean indexReady() {
+        return (mModel != null) && mModel.indexReady();
+    }
+
+    @Override
+    public int getCount() {
+        return mModel != null ? mModel.size() : 0;
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mModel.get(position, mSortOrder);
+    }
+
+    @Override
+    public boolean areAllItemsEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return true;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return 2;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        // If the position is the first in its section, then it corresponds to
+        // a title tile, if not it's a media tile
+        if (position == getPositionForSection(getSectionForPosition(position))) {
+            return ITEM_TYPE_BUCKET;
+        } else {
+            return ITEM_TYPE_MEDIA;
+        }
+    }
+
+    public boolean itemAtPositionIsBucket(int position) {
+        return getItemViewType(position) == ITEM_TYPE_BUCKET;
+    }
+
+    public boolean itemAtPositionIsMedia(int position) {
+        return getItemViewType(position) == ITEM_TYPE_MEDIA;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        int type = getItemViewType(position);
+        if (type == ITEM_TYPE_MEDIA) {
+            MtpThumbnailTileView imageView;
+            if (convertView == null) {
+                imageView = (MtpThumbnailTileView) mInflater.inflate(
+                        R.layout.ingest_thumbnail, parent, false);
+            } else {
+                imageView = (MtpThumbnailTileView) convertView;
+            }
+            imageView.setMtpDeviceAndObjectInfo(mModel.getDevice(), (MtpObjectInfo)getItem(position), mGeneration);
+            return imageView;
+        } else {
+            DateTileView dateTile;
+            if (convertView == null) {
+                dateTile = (DateTileView) mInflater.inflate(
+                        R.layout.ingest_date_tile, parent, false);
+            } else {
+                dateTile = (DateTileView) convertView;
+            }
+            dateTile.setDate((SimpleDate)getItem(position));
+            return dateTile;
+        }
+    }
+
+    @Override
+    public int getPositionForSection(int section) {
+        if (getCount() == 0) {
+            return 0;
+        }
+        int numSections = getSections().length;
+        if (section >= numSections) {
+            section = numSections - 1;
+        }
+        return mModel.getFirstPositionForBucketNumber(section, mSortOrder);
+    }
+
+    @Override
+    public int getSectionForPosition(int position) {
+        int count = getCount();
+        if (count == 0) {
+            return 0;
+        }
+        if (position >= count) {
+            position = count - 1;
+        }
+        return mModel.getBucketNumberForPosition(position, mSortOrder);
+    }
+
+    @Override
+    public Object[] getSections() {
+        return getCount() > 0 ? mModel.getBuckets(mSortOrder) : null;
+    }
+
+    public SortOrder getSortOrder() {
+        return mSortOrder;
+    }
+
+    public int translatePositionWithoutLabels(int position) {
+        if (mModel == null) return -1;
+        return mModel.getPositionFromPositionWithoutLabels(position, mSortOrder);
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java b/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java
new file mode 100644
index 0000000..9e7abc0
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/adapter/MtpPagerAdapter.java
@@ -0,0 +1,102 @@
+/*
+ * 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.gallery3d.ingest.adapter;
+
+import android.content.Context;
+import android.mtp.MtpObjectInfo;
+import android.support.v4.view.PagerAdapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.MtpDeviceIndex;
+import com.android.gallery3d.ingest.MtpDeviceIndex.SortOrder;
+import com.android.gallery3d.ingest.ui.MtpFullscreenView;
+
+public class MtpPagerAdapter extends PagerAdapter {
+
+    private LayoutInflater mInflater;
+    private int mGeneration = 0;
+    private CheckBroker mBroker;
+    private MtpDeviceIndex mModel;
+    private SortOrder mSortOrder = SortOrder.Descending;
+
+    private MtpFullscreenView mReusableView = null;
+
+    public MtpPagerAdapter(Context context, CheckBroker broker) {
+        super();
+        mInflater = LayoutInflater.from(context);
+        mBroker = broker;
+    }
+
+    public void setMtpDeviceIndex(MtpDeviceIndex index) {
+        mModel = index;
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public int getCount() {
+        return mModel != null ? mModel.sizeWithoutLabels() : 0;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        mGeneration++;
+        super.notifyDataSetChanged();
+    }
+
+    public int translatePositionWithLabels(int position) {
+        if (mModel == null) return -1;
+        return mModel.getPositionWithoutLabelsFromPosition(position, mSortOrder);
+    }
+
+    @Override
+    public void finishUpdate(ViewGroup container) {
+        mReusableView = null;
+        super.finishUpdate(container);
+    }
+
+    @Override
+    public boolean isViewFromObject(View view, Object object) {
+        return view == object;
+    }
+
+    @Override
+    public void destroyItem(ViewGroup container, int position, Object object) {
+        MtpFullscreenView v = (MtpFullscreenView)object;
+        container.removeView(v);
+        mBroker.unregisterOnCheckedChangeListener(v);
+        mReusableView = v;
+    }
+
+    @Override
+    public Object instantiateItem(ViewGroup container, int position) {
+        MtpFullscreenView v;
+        if (mReusableView != null) {
+            v = mReusableView;
+            mReusableView = null;
+        } else {
+            v = (MtpFullscreenView) mInflater.inflate(R.layout.ingest_fullsize, container, false);
+        }
+        MtpObjectInfo i = mModel.getWithoutLabels(position, mSortOrder);
+        v.getImageView().setMtpDeviceAndObjectInfo(mModel.getDevice(), i, mGeneration);
+        v.setPositionAndBroker(position, mBroker);
+        container.addView(v);
+        return v;
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java b/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
new file mode 100644
index 0000000..bbc90f6
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/BitmapWithMetadata.java
@@ -0,0 +1,29 @@
+/*
+ * 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.gallery3d.ingest.data;
+
+import android.graphics.Bitmap;
+
+public class BitmapWithMetadata {
+    public Bitmap bitmap;
+    public int rotationDegrees;
+
+    public BitmapWithMetadata(Bitmap bitmap, int rotationDegrees) {
+        this.bitmap = bitmap;
+        this.rotationDegrees = rotationDegrees;
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
new file mode 100644
index 0000000..30868c2
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
@@ -0,0 +1,106 @@
+/*
+ * 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.gallery3d.ingest.data;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.android.camera.Exif;
+import com.android.photos.data.GalleryBitmapPool;
+
+public class MtpBitmapFetch {
+    private static int sMaxSize = 0;
+
+    public static void recycleThumbnail(Bitmap b) {
+        if (b != null) {
+            GalleryBitmapPool.getInstance().put(b);
+        }
+    }
+
+    public static Bitmap getThumbnail(MtpDevice device, MtpObjectInfo info) {
+        byte[] imageBytes = device.getThumbnail(info.getObjectHandle());
+        if (imageBytes == null) {
+            return null;
+        }
+        BitmapFactory.Options o = new BitmapFactory.Options();
+        o.inJustDecodeBounds = true;
+        BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+        if (o.outWidth == 0 || o.outHeight == 0) {
+            return null;
+        }
+        o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
+        o.inMutable = true;
+        o.inJustDecodeBounds = false;
+        o.inSampleSize = 1;
+        try {
+            return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+        } catch (IllegalArgumentException e) {
+            // BitmapFactory throws an exception rather than returning null
+            // when image decoding fails and an existing bitmap was supplied
+            // for recycling, even if the failure was not caused by the use
+            // of that bitmap.
+            return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
+        }
+    }
+
+    public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info) {
+        return getFullsize(device, info, sMaxSize);
+    }
+
+    public static BitmapWithMetadata getFullsize(MtpDevice device, MtpObjectInfo info, int maxSide) {
+        byte[] imageBytes = device.getObject(info.getObjectHandle(), info.getCompressedSize());
+        if (imageBytes == null) {
+            return null;
+        }
+        Bitmap created;
+        if (maxSide > 0) {
+            BitmapFactory.Options o = new BitmapFactory.Options();
+            o.inJustDecodeBounds = true;
+            BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+            int w = o.outWidth;
+            int h = o.outHeight;
+            int comp = Math.max(h, w);
+            int sampleSize = 1;
+            while ((comp >> 1) >= maxSide) {
+                comp = comp >> 1;
+                sampleSize++;
+            }
+            o.inSampleSize = sampleSize;
+            o.inJustDecodeBounds = false;
+            created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
+        } else {
+            created = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
+        }
+        if (created == null) {
+            return null;
+        }
+
+        return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
+    }
+
+    public static void configureForContext(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        sMaxSize = Math.max(metrics.heightPixels, metrics.widthPixels);
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/DateTileView.java b/src/com/android/gallery3d/ingest/ui/DateTileView.java
new file mode 100644
index 0000000..52fe9b8
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ui/DateTileView.java
@@ -0,0 +1,107 @@
+/*
+ * 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.gallery3d.ingest.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.SimpleDate;
+
+import java.text.DateFormatSymbols;
+import java.util.Locale;
+
+public class DateTileView extends FrameLayout {
+    private static String[] sMonthNames = DateFormatSymbols.getInstance().getShortMonths();
+    private static Locale sLocale;
+
+    static {
+        refreshLocale();
+    }
+
+    public static boolean refreshLocale() {
+        Locale currentLocale = Locale.getDefault();
+        if (!currentLocale.equals(sLocale)) {
+            sLocale = currentLocale;
+            sMonthNames = DateFormatSymbols.getInstance(sLocale).getShortMonths();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private TextView mDateTextView;
+    private TextView mMonthTextView;
+    private TextView mYearTextView;
+    private int mMonth = -1;
+    private int mYear = -1;
+    private int mDate = -1;
+    private String[] mMonthNames = sMonthNames;
+
+    public DateTileView(Context context) {
+        super(context);
+    }
+
+    public DateTileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DateTileView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Force this to be square
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mDateTextView = (TextView) findViewById(R.id.date_tile_day);
+        mMonthTextView = (TextView) findViewById(R.id.date_tile_month);
+        mYearTextView = (TextView) findViewById(R.id.date_tile_year);
+    }
+
+    public void setDate(SimpleDate date) {
+        setDate(date.getDay(), date.getMonth(), date.getYear());
+    }
+
+    public void setDate(int date, int month, int year) {
+        if (date != mDate) {
+            mDate = date;
+            mDateTextView.setText(mDate > 9 ? Integer.toString(mDate) : "0" + mDate);
+        }
+        if (mMonthNames != sMonthNames) {
+            mMonthNames = sMonthNames;
+            if (month == mMonth) {
+                mMonthTextView.setText(mMonthNames[mMonth]);
+            }
+        }
+        if (month != mMonth) {
+            mMonth = month;
+            mMonthTextView.setText(mMonthNames[mMonth]);
+        }
+        if (year != mYear) {
+            mYear = year;
+            mYearTextView.setText(Integer.toString(mYear));
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/IngestGridView.java b/src/com/android/gallery3d/ingest/ui/IngestGridView.java
new file mode 100644
index 0000000..c821259
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ui/IngestGridView.java
@@ -0,0 +1,58 @@
+/*
+ * 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.gallery3d.ingest.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.GridView;
+
+/**
+ * This just extends GridView with the ability to listen for calls
+ * to clearChoices()
+ */
+public class IngestGridView extends GridView {
+
+    public interface OnClearChoicesListener {
+        public void onClearChoices();
+    }
+
+    private OnClearChoicesListener mOnClearChoicesListener = null;
+
+    public IngestGridView(Context context) {
+        super(context);
+    }
+
+    public IngestGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public IngestGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void setOnClearChoicesListener(OnClearChoicesListener l) {
+        mOnClearChoicesListener = l;
+    }
+
+    @Override
+    public void clearChoices() {
+        super.clearChoices();
+        if (mOnClearChoicesListener != null) {
+            mOnClearChoicesListener.onClearChoices();
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java b/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
new file mode 100644
index 0000000..8d3884d
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ui/MtpFullscreenView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.gallery3d.ingest.ui;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+import android.widget.Checkable;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.RelativeLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.adapter.CheckBroker;
+
+public class MtpFullscreenView extends RelativeLayout implements Checkable,
+    CompoundButton.OnCheckedChangeListener, CheckBroker.OnCheckedChangedListener {
+
+    private MtpImageView mImageView;
+    private CheckBox mCheckbox;
+    private int mPosition = -1;
+    private CheckBroker mBroker;
+
+    public MtpFullscreenView(Context context) {
+        super(context);
+    }
+
+    public MtpFullscreenView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MtpFullscreenView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = (MtpImageView) findViewById(R.id.ingest_fullsize_image);
+        mCheckbox = (CheckBox) findViewById(R.id.ingest_fullsize_image_checkbox);
+        mCheckbox.setOnCheckedChangeListener(this);
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mCheckbox.isChecked();
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mCheckbox.setChecked(checked);
+    }
+
+    @Override
+    public void toggle() {
+        mCheckbox.toggle();
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        setPositionAndBroker(-1, null);
+        super.onDetachedFromWindow();
+    }
+
+    public MtpImageView getImageView() {
+        return mImageView;
+    }
+
+    public int getPosition() {
+        return mPosition;
+    }
+
+    public void setPositionAndBroker(int position, CheckBroker b) {
+        if (mBroker != null) {
+            mBroker.unregisterOnCheckedChangeListener(this);
+        }
+        mPosition = position;
+        mBroker = b;
+        if (mBroker != null) {
+            setChecked(mBroker.isItemChecked(position));
+            mBroker.registerOnCheckedChangeListener(this);
+        }
+    }
+
+    @Override
+    public void onCheckedChanged(CompoundButton arg0, boolean isChecked) {
+        if (mBroker != null) mBroker.setItemChecked(mPosition, isChecked);
+    }
+
+    @Override
+    public void onCheckedChanged(int position, boolean isChecked) {
+        if (position == mPosition) {
+            setChecked(isChecked);
+        }
+    }
+
+    @Override
+    public void onBulkCheckedChanged() {
+        if(mBroker != null) setChecked(mBroker.isItemChecked(mPosition));
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpImageView.java b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
new file mode 100644
index 0000000..67414c6
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ui/MtpImageView.java
@@ -0,0 +1,227 @@
+/*
+ * 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.gallery3d.ingest.ui;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.gallery3d.ingest.data.BitmapWithMetadata;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+
+import java.lang.ref.WeakReference;
+
+public class MtpImageView extends ImageView {
+    private int mObjectHandle;
+    private int mGeneration;
+
+    private WeakReference<MtpImageView> mWeakReference = new WeakReference<MtpImageView>(this);
+    private Object mFetchLock = new Object();
+    private boolean mFetchPending = false;
+    private MtpObjectInfo mFetchObjectInfo;
+    private MtpDevice mFetchDevice;
+    private Object mFetchResult;
+
+    private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread();
+    private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler();
+
+    private void init() {
+         showPlaceholder();
+    }
+
+    public MtpImageView(Context context) {
+        super(context);
+        init();
+    }
+
+    public MtpImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MtpImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    private void showPlaceholder() {
+        setImageResource(android.R.color.transparent);
+    }
+
+    public void setMtpDeviceAndObjectInfo(MtpDevice device, MtpObjectInfo object, int gen) {
+        int handle = object.getObjectHandle();
+        if (handle == mObjectHandle && gen == mGeneration) {
+            return;
+        }
+        cancelLoadingAndClear();
+        showPlaceholder();
+        mGeneration = gen;
+        mObjectHandle = handle;
+        synchronized (mFetchLock) {
+            mFetchObjectInfo = object;
+            mFetchDevice = device;
+            if (mFetchPending) return;
+            mFetchPending = true;
+            sFetchHandler.sendMessage(
+                    sFetchHandler.obtainMessage(0, mWeakReference));
+        }
+    }
+
+    protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
+        return MtpBitmapFetch.getFullsize(device, info);
+    }
+
+    private float mLastBitmapWidth;
+    private float mLastBitmapHeight;
+    private int mLastRotationDegrees;
+    private Matrix mDrawMatrix = new Matrix();
+
+    private void updateDrawMatrix() {
+        mDrawMatrix.reset();
+        float dwidth;
+        float dheight;
+        float vheight = getHeight();
+        float vwidth = getWidth();
+        float scale;
+        boolean rotated90 = (mLastRotationDegrees % 180 != 0);
+        if (rotated90) {
+            dwidth = mLastBitmapHeight;
+            dheight = mLastBitmapWidth;
+        } else {
+            dwidth = mLastBitmapWidth;
+            dheight = mLastBitmapHeight;
+        }
+        if (dwidth <= vwidth && dheight <= vheight) {
+            scale = 1.0f;
+        } else {
+            scale = Math.min(vwidth / dwidth, vheight / dheight);
+        }
+        mDrawMatrix.setScale(scale, scale);
+        if (rotated90) {
+            mDrawMatrix.postTranslate(-dheight * scale * 0.5f,
+                    -dwidth * scale * 0.5f);
+            mDrawMatrix.postRotate(mLastRotationDegrees);
+            mDrawMatrix.postTranslate(dwidth * scale * 0.5f,
+                    dheight * scale * 0.5f);
+        }
+        mDrawMatrix.postTranslate((vwidth - dwidth * scale) * 0.5f,
+                (vheight - dheight * scale) * 0.5f);
+        if (!rotated90 && mLastRotationDegrees > 0) {
+            // rotated by a multiple of 180
+            mDrawMatrix.postRotate(mLastRotationDegrees, vwidth / 2, vheight / 2);
+        }
+        setImageMatrix(mDrawMatrix);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (changed && getScaleType() == ScaleType.MATRIX) {
+            updateDrawMatrix();
+        }
+    }
+
+    protected void onMtpImageDataFetchedFromDevice(Object result) {
+        BitmapWithMetadata bitmapWithMetadata = (BitmapWithMetadata)result;
+        if (getScaleType() == ScaleType.MATRIX) {
+            mLastBitmapHeight = bitmapWithMetadata.bitmap.getHeight();
+            mLastBitmapWidth = bitmapWithMetadata.bitmap.getWidth();
+            mLastRotationDegrees = bitmapWithMetadata.rotationDegrees;
+            updateDrawMatrix();
+        } else {
+            setRotation(bitmapWithMetadata.rotationDegrees);
+        }
+        setAlpha(0f);
+        setImageBitmap(bitmapWithMetadata.bitmap);
+        animate().alpha(1f);
+    }
+
+    protected void cancelLoadingAndClear() {
+        synchronized (mFetchLock) {
+            mFetchDevice = null;
+            mFetchObjectInfo = null;
+            mFetchResult = null;
+        }
+        animate().cancel();
+        setImageResource(android.R.color.transparent);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        cancelLoadingAndClear();
+        super.onDetachedFromWindow();
+    }
+
+    private static class FetchImageHandler extends Handler {
+        public FetchImageHandler(Looper l) {
+            super(l);
+        }
+
+        public static FetchImageHandler createOnNewThread() {
+            HandlerThread t = new HandlerThread("MtpImageView Fetch");
+            t.start();
+            return new FetchImageHandler(t.getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            @SuppressWarnings("unchecked")
+            MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
+            if (parent == null) return;
+            MtpObjectInfo objectInfo;
+            MtpDevice device;
+            synchronized (parent.mFetchLock) {
+                parent.mFetchPending = false;
+                device = parent.mFetchDevice;
+                objectInfo = parent.mFetchObjectInfo;
+            }
+            if (device == null) return;
+            Object result = parent.fetchMtpImageDataFromDevice(device, objectInfo);
+            if (result == null) return;
+            synchronized (parent.mFetchLock) {
+                if (parent.mFetchObjectInfo != objectInfo) return;
+                parent.mFetchResult = result;
+                parent.mFetchDevice = null;
+                parent.mFetchObjectInfo = null;
+                sFetchCompleteHandler.sendMessage(
+                        sFetchCompleteHandler.obtainMessage(0, parent.mWeakReference));
+            }
+        }
+    }
+
+    private static class ShowImageHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            @SuppressWarnings("unchecked")
+            MtpImageView parent = ((WeakReference<MtpImageView>) msg.obj).get();
+            if (parent == null) return;
+            Object result;
+            synchronized (parent.mFetchLock) {
+                result = parent.mFetchResult;
+            }
+            if (result == null) return;
+            parent.onMtpImageDataFetchedFromDevice(result);
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java
new file mode 100644
index 0000000..3307e78
--- /dev/null
+++ b/src/com/android/gallery3d/ingest/ui/MtpThumbnailTileView.java
@@ -0,0 +1,106 @@
+/*
+ * 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.gallery3d.ingest.ui;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+import android.util.AttributeSet;
+import android.widget.Checkable;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.ingest.data.MtpBitmapFetch;
+
+
+public class MtpThumbnailTileView extends MtpImageView implements Checkable {
+
+    private Paint mForegroundPaint;
+    private boolean mIsChecked;
+    private Bitmap mBitmap;
+
+    private void init() {
+        mForegroundPaint = new Paint();
+        mForegroundPaint.setColor(getResources().getColor(R.color.ingest_highlight_semitransparent));
+    }
+
+    public MtpThumbnailTileView(Context context) {
+        super(context);
+        init();
+    }
+
+    public MtpThumbnailTileView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public MtpThumbnailTileView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    @Override
+    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // Force this to be square
+        super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+    }
+
+    @Override
+    protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) {
+        return MtpBitmapFetch.getThumbnail(device, info);
+    }
+
+    @Override
+    protected void onMtpImageDataFetchedFromDevice(Object result) {
+        mBitmap = (Bitmap)result;
+        setImageBitmap(mBitmap);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (isChecked()) {
+            canvas.drawRect(canvas.getClipBounds(), mForegroundPaint);
+        }
+    }
+
+    @Override
+    public boolean isChecked() {
+        return mIsChecked;
+    }
+
+    @Override
+    public void setChecked(boolean checked) {
+        mIsChecked = checked;
+    }
+
+    @Override
+    public void toggle() {
+        setChecked(!mIsChecked);
+    }
+
+    @Override
+    protected void cancelLoadingAndClear() {
+        super.cancelLoadingAndClear();
+        if (mBitmap != null) {
+            MtpBitmapFetch.recycleThumbnail(mBitmap);
+            mBitmap = null;
+        }
+    }
+}
diff --git a/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java
index 0187cba..ef26b1b 100644
--- a/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java
+++ b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java
@@ -18,11 +18,13 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.os.Build;
 import android.os.Environment;
 import android.preference.PreferenceManager;
 import android.util.Log;
 
 import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.LocalAlbum;
 import com.android.gallery3d.data.MediaSet;
@@ -36,34 +38,35 @@
 import java.util.List;
 
 /**
- * This one-timer migrates local-album gallery app widgets from pre-JB releases to JB (or later)
- * due to bucket ID (i.e., directory hash) change in JB (as the external storage path is changed
- * from /mnt/sdcard to /storage/sdcard0).
+ * This one-timer migrates local-album gallery app widgets from old paths from prior releases 
+ * to updated paths in the current build version. This migration is needed because of
+ * bucket ID (i.e., directory hash) change in JB and JB MR1 (The external storage path has changed
+ * from /mnt/sdcard in pre-JB releases, to /storage/sdcard0 in JB, then again
+ * to /external/storage/sdcard/0 in JB MR1).
  */
 public class GalleryWidgetMigrator {
     private static final String TAG = "GalleryWidgetMigrator";
-    private static final String OLD_EXT_PATH = "/mnt/sdcard";
+    private static final String PRE_JB_EXT_PATH = "/mnt/sdcard";
+    private static final String JB_EXT_PATH = "/storage/sdcard0";
     private static final String NEW_EXT_PATH =
             Environment.getExternalStorageDirectory().getAbsolutePath();
     private static final int RELATIVE_PATH_START = NEW_EXT_PATH.length();
-    private static final String KEY_MIGRATION_DONE = "gallery_widget_migration_done";
+    private static final String KEY_EXT_PATH = "external_storage_path";
 
     /**
-     * Migrates local-album gallery widgets from pre-JB releases to JB (or later) due to bucket ID
-     * (i.e., directory hash) change in JB.
+     * Migrates local-album gallery widgets from prior releases to current release
+     * due to bucket ID (i.e., directory hash) change.
      */
     public static void migrateGalleryWidgets(Context context) {
-        // no migration needed if path of external storage is not changed
-        if (OLD_EXT_PATH.equals(NEW_EXT_PATH)) return;
-
-        // only need to migrate once; the "done" bit is saved to SharedPreferences
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-        boolean isDone = prefs.getBoolean(KEY_MIGRATION_DONE, false);
+        // Migration is only needed when external storage path has changed
+        String extPath = prefs.getString(KEY_EXT_PATH, null);
+        boolean isDone = NEW_EXT_PATH.equals(extPath);
         if (isDone) return;
 
         try {
             migrateGalleryWidgetsInternal(context);
-            prefs.edit().putBoolean(KEY_MIGRATION_DONE, true).commit();
+            prefs.edit().putString(KEY_EXT_PATH, NEW_EXT_PATH).commit();
         } catch (Throwable t) {
             // exception may be thrown if external storage is not available(?)
             Log.w(TAG, "migrateGalleryWidgets", t);
@@ -77,39 +80,62 @@
 
         // only need to migrate local-album entries of type TYPE_ALBUM
         List<Entry> entries = dbHelper.getEntries(WidgetDatabaseHelper.TYPE_ALBUM);
-        if (entries != null) {
-            HashMap<Integer, Entry> localEntries = new HashMap<Integer, Entry>(entries.size());
-            for (Entry entry : entries) {
-                Path path = Path.fromString(entry.albumPath);
-                MediaSet mediaSet = (MediaSet) manager.getMediaObject(path);
-                if (mediaSet instanceof LocalAlbum) {
+        if (entries == null) return;
+
+        // Check each entry's relativePath. If exists, update bucket id using relative
+        // path combined with external storage path. Otherwise, iterate through old external
+        // storage paths to find the relative path that matches the old bucket id, and then update
+        // bucket id and relative path
+        HashMap<Integer, Entry> localEntries = new HashMap<Integer, Entry>(entries.size());
+        for (Entry entry : entries) {
+            Path path = Path.fromString(entry.albumPath);
+            MediaSet mediaSet = (MediaSet) manager.getMediaObject(path);
+            if (mediaSet instanceof LocalAlbum) {
+                if (entry.relativePath != null && entry.relativePath.length() > 0) {
+                    // update entry using relative path + external storage path
+                    updateEntryUsingRelativePath(entry, dbHelper);
+                } else {
                     int bucketId = Integer.parseInt(path.getSuffix());
                     localEntries.put(bucketId, entry);
                 }
             }
-            if (!localEntries.isEmpty()) migrateLocalEntries(localEntries, dbHelper);
+        }
+        if (!localEntries.isEmpty()) migrateLocalEntries(context, localEntries, dbHelper);
+    }
+
+    private static void migrateLocalEntries(Context context,
+            HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) {
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        String oldExtPath = prefs.getString(KEY_EXT_PATH, null);
+        if (oldExtPath != null) {
+            migrateLocalEntries(entries, dbHelper, oldExtPath);
+            return;
+        }
+        // If old external storage path is unknown, it could be either Pre-JB or JB version
+        // we need to try both.
+        migrateLocalEntries(entries, dbHelper, PRE_JB_EXT_PATH);
+        if (!entries.isEmpty() &&
+                Build.VERSION.SDK_INT > ApiHelper.VERSION_CODES.JELLY_BEAN) {
+            migrateLocalEntries(entries, dbHelper, JB_EXT_PATH);
         }
     }
 
-    private static void migrateLocalEntries(
-            HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) {
+    private static void migrateLocalEntries(HashMap<Integer, Entry> entries,
+             WidgetDatabaseHelper dbHelper, String oldExtPath) {
         File root = Environment.getExternalStorageDirectory();
-
         // check the DCIM directory first; this should take care of 99% use cases
-        updatePath(new File(root, "DCIM"), entries, dbHelper);
-
+        updatePath(new File(root, "DCIM"), entries, dbHelper, oldExtPath);
         // check other directories if DCIM doesn't cut it
-        if (!entries.isEmpty()) updatePath(root, entries, dbHelper);
+        if (!entries.isEmpty()) updatePath(root, entries, dbHelper, oldExtPath);
     }
-
-    private static void updatePath(
-            File root, HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) {
+    private static void updatePath(File root, HashMap<Integer, Entry> entries,
+            WidgetDatabaseHelper dbHelper, String oldExtStorage) {
         File[] files = root.listFiles();
         if (files != null) {
             for (File file : files) {
                 if (file.isDirectory() && !entries.isEmpty()) {
                     String path = file.getAbsolutePath();
-                    String oldPath = OLD_EXT_PATH + path.substring(RELATIVE_PATH_START);
+                    String oldPath = oldExtStorage + path.substring(RELATIVE_PATH_START);
                     int oldBucketId = GalleryUtils.getBucketId(oldPath);
                     Entry entry = entries.remove(oldBucketId);
                     if (entry != null) {
@@ -120,11 +146,24 @@
                                 .toString();
                         Log.d(TAG, "migrate from " + entry.albumPath + " to " + newAlbumPath);
                         entry.albumPath = newAlbumPath;
+                        // update entry's relative path
+                        entry.relativePath = path.substring(RELATIVE_PATH_START);
                         dbHelper.updateEntry(entry);
                     }
-                    updatePath(file, entries, dbHelper); // recursion
+                    updatePath(file, entries, dbHelper, oldExtStorage); // recursion
                 }
             }
         }
     }
+
+    private static void updateEntryUsingRelativePath(Entry entry, WidgetDatabaseHelper dbHelper) {
+        String newPath = NEW_EXT_PATH + entry.relativePath;
+        int newBucketId = GalleryUtils.getBucketId(newPath);
+        String newAlbumPath = Path.fromString(entry.albumPath)
+                .getParent()
+                .getChild(newBucketId)
+                .toString();
+        entry.albumPath = newAlbumPath;
+        dbHelper.updateEntry(entry);
+    }
 }
diff --git a/src/com/android/gallery3d/provider/GalleryProvider.java b/src/com/android/gallery3d/provider/GalleryProvider.java
index 45f47a4..d6c7ccd 100644
--- a/src/com/android/gallery3d/provider/GalleryProvider.java
+++ b/src/com/android/gallery3d/provider/GalleryProvider.java
@@ -34,14 +34,12 @@
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.MtpImage;
 import com.android.gallery3d.data.Path;
 import com.android.gallery3d.picasasource.PicasaSource;
 import com.android.gallery3d.util.GalleryUtils;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.OutputStream;
 
 public class GalleryProvider extends ContentProvider {
     private static final String TAG = "GalleryProvider";
@@ -126,9 +124,6 @@
             if (PicasaSource.isPicasaImage(object)) {
                 return queryPicasaItem(object,
                         projection, selection, selectionArgs, sortOrder);
-            } else if (object instanceof MtpImage) {
-                return queryMtpItem((MtpImage) object,
-                        projection, selection, selectionArgs, sortOrder);
             } else {
                     return null;
             }
@@ -137,28 +132,6 @@
         }
     }
 
-    private Cursor queryMtpItem(MtpImage image, String[] projection,
-            String selection, String[] selectionArgs, String sortOrder) {
-        Object[] columnValues = new Object[projection.length];
-        for (int i = 0, n = projection.length; i < n; ++i) {
-            String column = projection[i];
-            if (ImageColumns.DISPLAY_NAME.equals(column)) {
-                columnValues[i] = image.getName();
-            } else if (ImageColumns.SIZE.equals(column)){
-                columnValues[i] = image.getSize();
-            } else if (ImageColumns.MIME_TYPE.equals(column)) {
-                columnValues[i] = image.getMimeType();
-            } else if (ImageColumns.DATE_TAKEN.equals(column)) {
-                columnValues[i] = image.getDateInMs();
-            } else {
-                Log.w(TAG, "unsupported column: " + column);
-            }
-        }
-        MatrixCursor cursor = new MatrixCursor(projection);
-        cursor.addRow(columnValues);
-        return cursor;
-    }
-
     private Cursor queryPicasaItem(MediaObject image, String[] projection,
             String selection, String[] selectionArgs, String sortOrder) {
         if (projection == null) projection = SUPPORTED_PICASA_COLUMNS;
@@ -211,9 +184,6 @@
             }
             if (PicasaSource.isPicasaImage(object)) {
                 return PicasaSource.openFile(getContext(), object, mode);
-            } else if (object instanceof MtpImage) {
-                return openPipeHelper(null,
-                        new MtpPipeDataWriter((MtpImage) object));
             } else {
                 throw new FileNotFoundException("unspported type: " + object);
             }
@@ -255,24 +225,4 @@
         }
     }
 
-    private final class MtpPipeDataWriter implements PipeDataWriter<Object> {
-        private final MtpImage mImage;
-
-        private MtpPipeDataWriter(MtpImage image) {
-            mImage = image;
-        }
-
-        @Override
-        public void writeDataToPipe(ParcelFileDescriptor output, Object args) {
-            OutputStream os = null;
-            try {
-                os = new ParcelFileDescriptor.AutoCloseOutputStream(output);
-                os.write(mImage.getImageData());
-            } catch (IOException e) {
-                Log.w(TAG, "fail to download: " + mImage.toString(), e);
-            } finally {
-                Utils.closeSilently(os);
-            }
-        }
-    }
 }
diff --git a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java
index 10b710d..729439d 100644
--- a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java
+++ b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java
@@ -20,6 +20,11 @@
 import android.graphics.Rect;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.glrenderer.FadeOutTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.NinePatchTexture;
+import com.android.gallery3d.glrenderer.ResourceTexture;
+import com.android.gallery3d.glrenderer.Texture;
 
 public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer {
 
diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java
index 7191599..6b4f103 100644
--- a/src/com/android/gallery3d/ui/ActionModeHandler.java
+++ b/src/com/android/gallery3d/ui/ActionModeHandler.java
@@ -53,9 +53,12 @@
     @SuppressWarnings("unused")
     private static final String TAG = "ActionModeHandler";
 
+    private static final int MAX_SELECTED_ITEMS_FOR_SHARE_INTENT = 300;
+    private static final int MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT = 10;
+
     private static final int SUPPORT_MULTIPLE_MASK = MediaObject.SUPPORT_DELETE
             | MediaObject.SUPPORT_ROTATE | MediaObject.SUPPORT_SHARE
-            | MediaObject.SUPPORT_CACHE | MediaObject.SUPPORT_IMPORT;
+            | MediaObject.SUPPORT_CACHE;
 
     public interface ActionModeListener {
         public boolean onActionItemClicked(MenuItem item);
@@ -173,9 +176,7 @@
             ProgressListener listener = null;
             String confirmMsg = null;
             int action = item.getItemId();
-            if (action == R.id.action_import) {
-                listener = new ImportCompleteListener(mActivity);
-            } else if (action == R.id.action_delete) {
+            if (action == R.id.action_delete) {
                 confirmMsg = mActivity.getResources().getQuantityString(
                         R.plurals.delete_selection, mSelectionManager.getSelectedCount());
                 if (mDeleteProgressListener == null) {
@@ -316,10 +317,10 @@
 
     // Share intent needs to expand the selection set so we can get URI of
     // each media item
-    private Intent computePanoramaSharingIntent(JobContext jc) {
-        ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
-        if (expandedPaths.size() == 0) {
-            return null;
+    private Intent computePanoramaSharingIntent(JobContext jc, int maxItems) {
+        ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true, maxItems);
+        if (expandedPaths == null || expandedPaths.size() == 0) {
+            return new Intent();
         }
         final ArrayList<Uri> uris = new ArrayList<Uri>();
         DataManager manager = mActivity.getDataManager();
@@ -346,11 +347,11 @@
         return intent;
     }
 
-    private Intent computeSharingIntent(JobContext jc) {
-        ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true);
-        if (expandedPaths.size() == 0) {
+    private Intent computeSharingIntent(JobContext jc, int maxItems) {
+        ArrayList<Path> expandedPaths = mSelectionManager.getSelected(true, maxItems);
+        if (expandedPaths == null || expandedPaths.size() == 0) {
             setNfcBeamPushUris(null);
-            return null;
+            return new Intent();
         }
         final ArrayList<Uri> uris = new ArrayList<Uri>();
         DataManager manager = mActivity.getDataManager();
@@ -408,20 +409,42 @@
                 // Pass1: Deal with unexpanded media object list for menu operation.
                 ArrayList<MediaObject> selected = getSelectedMediaObjects(jc);
                 if (selected == null) {
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mMenuTask = null;
+                            if (jc.isCancelled()) return;
+                            // Disable all the operations when no item is selected
+                            MenuExecutor.updateMenuOperation(mMenu, 0);
+                        }
+                    });
                     return null;
                 }
                 final int operation = computeMenuOptions(selected);
                 if (jc.isCancelled()) {
                     return null;
                 }
-                final GetAllPanoramaSupports supportCallback = new GetAllPanoramaSupports(selected,
-                        jc);
+                int numSelected = selected.size();
+                final boolean canSharePanoramas =
+                        numSelected < MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT;
+                final boolean canShare =
+                        numSelected < MAX_SELECTED_ITEMS_FOR_SHARE_INTENT;
+
+                final GetAllPanoramaSupports supportCallback = canSharePanoramas ?
+                        new GetAllPanoramaSupports(selected, jc)
+                        : null;
 
                 // Pass2: Deal with expanded media object list for sharing operation.
-                final Intent share_panorama_intent = computePanoramaSharingIntent(jc);
-                final Intent share_intent = computeSharingIntent(jc);
+                final Intent share_panorama_intent = canSharePanoramas ?
+                        computePanoramaSharingIntent(jc, MAX_SELECTED_ITEMS_FOR_PANORAMA_SHARE_INTENT)
+                        : new Intent();
+                final Intent share_intent = canShare ?
+                        computeSharingIntent(jc, MAX_SELECTED_ITEMS_FOR_SHARE_INTENT)
+                        : new Intent();
 
-                supportCallback.waitForPanoramaSupport();
+                if (canSharePanoramas) {
+                    supportCallback.waitForPanoramaSupport();
+                }
                 if (jc.isCancelled()) {
                     return null;
                 }
@@ -431,11 +454,12 @@
                         mMenuTask = null;
                         if (jc.isCancelled()) return;
                         MenuExecutor.updateMenuOperation(mMenu, operation);
-                        MenuExecutor.updateMenuForPanorama(mMenu, supportCallback.mAllPanorama360,
-                                supportCallback.mHasPanorama360);
+                        MenuExecutor.updateMenuForPanorama(mMenu,
+                                canSharePanoramas && supportCallback.mAllPanorama360,
+                                canSharePanoramas && supportCallback.mHasPanorama360);
                         if (mSharePanoramaMenuItem != null) {
                             mSharePanoramaMenuItem.setEnabled(true);
-                            if (supportCallback.mAllPanorama360) {
+                            if (canSharePanoramas && supportCallback.mAllPanorama360) {
                                 mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                                 mShareMenuItem.setTitle(
                                     mActivity.getResources().getString(R.string.share_as_photo));
@@ -448,7 +472,7 @@
                             mSharePanoramaActionProvider.setShareIntent(share_panorama_intent);
                         }
                         if (mShareMenuItem != null) {
-                            mShareMenuItem.setEnabled(true);
+                            mShareMenuItem.setEnabled(canShare);
                             mShareActionProvider.setShareIntent(share_intent);
                         }
                     }
@@ -466,7 +490,12 @@
         mMenuExecutor.pause();
     }
 
+    public void destroy() {
+        mMenuExecutor.destroy();
+    }
+
     public void resume() {
         if (mSelectionManager.inSelectionMode()) updateSupportedOperation();
+        mMenuExecutor.resume();
     }
 }
diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
index cd6c778..da1cac0 100644
--- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java
+++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java
@@ -27,8 +27,8 @@
 import android.text.TextUtils;
 
 import com.android.gallery3d.R;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
@@ -41,12 +41,12 @@
     private final Context mContext;
 
     private int mLabelWidth;
-    private BitmapPool mBitmapPool;
+    private int mBitmapWidth;
+    private int mBitmapHeight;
 
     private final LazyLoadedBitmap mLocalSetIcon;
     private final LazyLoadedBitmap mPicasaIcon;
     private final LazyLoadedBitmap mCameraIcon;
-    private final LazyLoadedBitmap mMtpIcon;
 
     public AlbumLabelMaker(Context context, AlbumSetSlotRenderer.LabelSpec spec) {
         mContext = context;
@@ -57,7 +57,6 @@
         mLocalSetIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_folder);
         mPicasaIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_picasa);
         mCameraIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_camera);
-        mMtpIcon = new LazyLoadedBitmap(R.drawable.frame_overlay_gallery_ptp);
     }
 
     public static int getBorderSize() {
@@ -70,8 +69,6 @@
                 return mCameraIcon.get();
             case DataSourceType.TYPE_LOCAL:
                 return mLocalSetIcon.get();
-            case DataSourceType.TYPE_MTP:
-                return mMtpIcon.get();
             case DataSourceType.TYPE_PICASA:
                 return mPicasaIcon.get();
         }
@@ -113,8 +110,8 @@
         if (mLabelWidth == width) return;
         mLabelWidth = width;
         int borders = 2 * BORDER_SIZE;
-        mBitmapPool = new BitmapPool(
-                width + borders, mSpec.labelBackgroundHeight + borders, 16);
+        mBitmapWidth = width + borders;
+        mBitmapHeight = mSpec.labelBackgroundHeight + borders;
     }
 
     public ThreadPool.Job<Bitmap> requestLabel(
@@ -156,7 +153,7 @@
 
             synchronized (this) {
                 labelWidth = mLabelWidth;
-                bitmap = mBitmapPool.getBitmap();
+                bitmap = GalleryBitmapPool.getInstance().get(mBitmapWidth, mBitmapHeight);
             }
 
             if (bitmap == null) {
@@ -204,10 +201,6 @@
     }
 
     public void recycleLabel(Bitmap label) {
-        mBitmapPool.recycle(label);
-    }
-
-    public void clearRecycledLabels() {
-        if (mBitmapPool != null) mBitmapPool.clear();
+        GalleryBitmapPool.getInstance().put(label);
     }
 }
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
index 80dfc91..8149df4 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
@@ -23,12 +23,15 @@
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumSetDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.Texture;
+import com.android.gallery3d.glrenderer.TextureUploader;
+import com.android.gallery3d.glrenderer.TiledTexture;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 import com.android.gallery3d.util.ThreadPool;
@@ -399,7 +402,6 @@
         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
             freeSlotContent(i);
         }
-        mLabelMaker.clearRecycledLabels();
     }
 
     public void resume() {
@@ -425,12 +427,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(mMediaItem.requestImage(
                     MediaItem.TYPE_MICROTHUMBNAIL), l);
@@ -501,11 +497,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            mLabelMaker.recycleLabel(bitmap);
-        }
-
-        @Override
         protected void onLoadComplete(Bitmap bitmap) {
             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
         }
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
index 70d7c27..5332ef8 100644
--- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
+++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java
@@ -21,6 +21,13 @@
 import com.android.gallery3d.app.AlbumSetDataLoader;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.ColorTexture;
+import com.android.gallery3d.glrenderer.FadeInTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.ResourceTexture;
+import com.android.gallery3d.glrenderer.Texture;
+import com.android.gallery3d.glrenderer.TiledTexture;
+import com.android.gallery3d.glrenderer.UploadedTexture;
 import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry;
 
 public class AlbumSetSlotRenderer extends AbstractSlotRenderer {
diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index 678c432..fec7d1e 100644
--- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -22,11 +22,12 @@
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.Texture;
+import com.android.gallery3d.glrenderer.TiledTexture;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 import com.android.gallery3d.util.JobLimiter;
@@ -294,12 +295,6 @@
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(
                     mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
diff --git a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
index ce5b7ac..dc6c89b 100644
--- a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
+++ b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java
@@ -20,6 +20,11 @@
 import com.android.gallery3d.app.AlbumDataLoader;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.ColorTexture;
+import com.android.gallery3d.glrenderer.FadeInTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.Texture;
+import com.android.gallery3d.glrenderer.TiledTexture;
 
 public class AlbumSlotRenderer extends AbstractSlotRenderer {
     @SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java
deleted file mode 100644
index 99cf057..0000000
--- a/src/com/android/gallery3d/ui/BasicTexture.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import com.android.gallery3d.common.Utils;
-
-import java.util.WeakHashMap;
-
-// BasicTexture is a Texture corresponds to a real GL texture.
-// The state of a BasicTexture indicates whether its data is loaded to GL memory.
-// If a BasicTexture is loaded into GL memory, it has a GL texture id.
-abstract class BasicTexture implements Texture {
-
-    @SuppressWarnings("unused")
-    private static final String TAG = "BasicTexture";
-    protected static final int UNSPECIFIED = -1;
-
-    protected static final int STATE_UNLOADED = 0;
-    protected static final int STATE_LOADED = 1;
-    protected static final int STATE_ERROR = -1;
-
-    // Log a warning if a texture is larger along a dimension
-    private static final int MAX_TEXTURE_SIZE = 4096;
-
-    protected int mId;
-    protected int mState;
-
-    protected int mWidth = UNSPECIFIED;
-    protected int mHeight = UNSPECIFIED;
-
-    protected int mTextureWidth;
-    protected int mTextureHeight;
-
-    private boolean mHasBorder;
-
-    protected GLCanvas mCanvasRef = null;
-    private static WeakHashMap<BasicTexture, Object> sAllTextures
-            = new WeakHashMap<BasicTexture, Object>();
-    private static ThreadLocal sInFinalizer = new ThreadLocal();
-
-    protected BasicTexture(GLCanvas canvas, int id, int state) {
-        setAssociatedCanvas(canvas);
-        mId = id;
-        mState = state;
-        synchronized (sAllTextures) {
-            sAllTextures.put(this, null);
-        }
-    }
-
-    protected BasicTexture() {
-        this(null, 0, STATE_UNLOADED);
-    }
-
-    protected void setAssociatedCanvas(GLCanvas canvas) {
-        mCanvasRef = canvas;
-    }
-
-    /**
-     * Sets the content size of this texture. In OpenGL, the actual texture
-     * size must be of power of 2, the size of the content may be smaller.
-     */
-    protected void setSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
-        mTextureWidth = Utils.nextPowerOf2(width);
-        mTextureHeight = Utils.nextPowerOf2(height);
-        if (mTextureWidth > MAX_TEXTURE_SIZE || mTextureHeight > MAX_TEXTURE_SIZE) {
-            Log.w(TAG, String.format("texture is too large: %d x %d",
-                    mTextureWidth, mTextureHeight), new Exception());
-        }
-    }
-
-    public int getId() {
-        return mId;
-    }
-
-    @Override
-    public int getWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        return mHeight;
-    }
-
-    // Returns the width rounded to the next power of 2.
-    public int getTextureWidth() {
-        return mTextureWidth;
-    }
-
-    // Returns the height rounded to the next power of 2.
-    public int getTextureHeight() {
-        return mTextureHeight;
-    }
-
-    // Returns true if the texture has one pixel transparent border around the
-    // actual content. This is used to avoid jigged edges.
-    //
-    // The jigged edges appear because we use GL_CLAMP_TO_EDGE for texture wrap
-    // mode (GL_CLAMP is not available in OpenGL ES), so a pixel partially
-    // covered by the texture will use the color of the edge texel. If we add
-    // the transparent border, the color of the edge texel will be mixed with
-    // appropriate amount of transparent.
-    //
-    // Currently our background is black, so we can draw the thumbnails without
-    // enabling blending.
-    public boolean hasBorder() {
-        return mHasBorder;
-    }
-
-    protected void setBorder(boolean hasBorder) {
-        mHasBorder = hasBorder;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y) {
-        canvas.drawTexture(this, x, y, getWidth(), getHeight());
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
-        canvas.drawTexture(this, x, y, w, h);
-    }
-
-    // onBind is called before GLCanvas binds this texture.
-    // It should make sure the data is uploaded to GL memory.
-    abstract protected boolean onBind(GLCanvas canvas);
-
-    // Returns the GL texture target for this texture (e.g. GL_TEXTURE_2D).
-    abstract protected int getTarget();
-
-    public boolean isLoaded() {
-        return mState == STATE_LOADED;
-    }
-
-    // recycle() is called when the texture will never be used again,
-    // so it can free all resources.
-    public void recycle() {
-        freeResource();
-    }
-
-    // yield() is called when the texture will not be used temporarily,
-    // so it can free some resources.
-    // The default implementation unloads the texture from GL memory, so
-    // the subclass should make sure it can reload the texture to GL memory
-    // later, or it will have to override this method.
-    public void yield() {
-        freeResource();
-    }
-
-    private void freeResource() {
-        GLCanvas canvas = mCanvasRef;
-        if (canvas != null && isLoaded()) {
-            canvas.unloadTexture(this);
-        }
-        mState = STATE_UNLOADED;
-        setAssociatedCanvas(null);
-    }
-
-    @Override
-    protected void finalize() {
-        sInFinalizer.set(BasicTexture.class);
-        recycle();
-        sInFinalizer.set(null);
-    }
-
-    // This is for deciding if we can call Bitmap's recycle().
-    // We cannot call Bitmap's recycle() in finalizer because at that point
-    // the finalizer of Bitmap may already be called so recycle() will crash.
-    public static boolean inFinalizer() {
-        return sInFinalizer.get() != null;
-    }
-
-    public static void yieldAllTextures() {
-        synchronized (sAllTextures) {
-            for (BasicTexture t : sAllTextures.keySet()) {
-                t.yield();
-            }
-        }
-    }
-
-    public static void invalidateAllTextures() {
-        synchronized (sAllTextures) {
-            for (BasicTexture t : sAllTextures.keySet()) {
-                t.mState = STATE_UNLOADED;
-                t.setAssociatedCanvas(null);
-            }
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/BitmapLoader.java b/src/com/android/gallery3d/ui/BitmapLoader.java
index 4f07cc0..a708a90 100644
--- a/src/com/android/gallery3d/ui/BitmapLoader.java
+++ b/src/com/android/gallery3d/ui/BitmapLoader.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 
@@ -51,7 +52,7 @@
             mBitmap = future.get();
             if (mState == STATE_RECYCLED) {
                 if (mBitmap != null) {
-                    recycleBitmap(mBitmap);
+                    GalleryBitmapPool.getInstance().put(mBitmap);
                     mBitmap = null;
                 }
                 return; // don't call callback
@@ -84,7 +85,7 @@
     public synchronized void recycle() {
         mState = STATE_RECYCLED;
         if (mBitmap != null) {
-            recycleBitmap(mBitmap);
+            GalleryBitmapPool.getInstance().put(mBitmap);
             mBitmap = null;
         }
         if (mTask != null) mTask.cancel();
@@ -103,6 +104,5 @@
     }
 
     abstract protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l);
-    abstract protected void recycleBitmap(Bitmap bitmap);
     abstract protected void onLoadComplete(Bitmap bitmap);
 }
diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java
index 741eefb..a3d4039 100644
--- a/src/com/android/gallery3d/ui/BitmapScreenNail.java
+++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java
@@ -19,6 +19,9 @@
 import android.graphics.Bitmap;
 import android.graphics.RectF;
 
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+
 public class BitmapScreenNail implements ScreenNail {
     private final BitmapTexture mBitmapTexture;
 
diff --git a/src/com/android/gallery3d/ui/BitmapTexture.java b/src/com/android/gallery3d/ui/BitmapTexture.java
deleted file mode 100644
index 6075449..0000000
--- a/src/com/android/gallery3d/ui/BitmapTexture.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.common.Utils;
-
-// BitmapTexture is a texture whose content is specified by a fixed Bitmap.
-//
-// The texture does not own the Bitmap. The user should make sure the Bitmap
-// is valid during the texture's lifetime. When the texture is recycled, it
-// does not free the Bitmap.
-public class BitmapTexture extends UploadedTexture {
-    protected Bitmap mContentBitmap;
-
-    public BitmapTexture(Bitmap bitmap) {
-        this(bitmap, false);
-    }
-
-    public BitmapTexture(Bitmap bitmap, boolean hasBorder) {
-        super(hasBorder);
-        Utils.assertTrue(bitmap != null && !bitmap.isRecycled());
-        mContentBitmap = bitmap;
-    }
-
-    @Override
-    protected void onFreeBitmap(Bitmap bitmap) {
-        // Do nothing.
-    }
-
-    @Override
-    protected Bitmap onGetBitmap() {
-        return mContentBitmap;
-    }
-
-    public Bitmap getBitmap() {
-        return mContentBitmap;
-    }
-}
diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java
index d4c9b1d..e1a8b76 100644
--- a/src/com/android/gallery3d/ui/BitmapTileProvider.java
+++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java
@@ -21,11 +21,11 @@
 import android.graphics.Canvas;
 
 import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 import java.util.ArrayList;
 
-public class BitmapTileProvider implements TileImageView.Model {
+public class BitmapTileProvider implements TileImageView.TileSource {
     private final ScreenNail mScreenNail;
     private final Bitmap[] mMipmaps;
     private final Config mConfig;
@@ -71,23 +71,21 @@
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize,
-            int borderSize, BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         x >>= level;
         y >>= level;
-        int size = tileSize + 2 * borderSize;
 
-        Bitmap result = pool == null ? null : pool.getBitmap();
+        Bitmap result = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (result == null) {
-            result = Bitmap.createBitmap(size, size, mConfig);
+            result = Bitmap.createBitmap(tileSize, tileSize, mConfig);
         } else {
             result.eraseColor(0);
         }
 
         Bitmap mipmap = mMipmaps[level];
         Canvas canvas = new Canvas(result);
-        int offsetX = -x + borderSize;
-        int offsetY = -y + borderSize;
+        int offsetX = -x;
+        int offsetY = -y;
         canvas.drawBitmap(mipmap, offsetX, offsetY, null);
         return result;
     }
diff --git a/src/com/android/gallery3d/ui/CanvasTexture.java b/src/com/android/gallery3d/ui/CanvasTexture.java
deleted file mode 100644
index a2e9e48..0000000
--- a/src/com/android/gallery3d/ui/CanvasTexture.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-
-// CanvasTexture is a texture whose content is the drawing on a Canvas.
-// The subclasses should override onDraw() to draw on the bitmap.
-// By default CanvasTexture is not opaque.
-abstract class CanvasTexture extends UploadedTexture {
-    protected Canvas mCanvas;
-    private final Config mConfig;
-
-    public CanvasTexture(int width, int height) {
-        mConfig = Config.ARGB_8888;
-        setSize(width, height);
-        setOpaque(false);
-    }
-
-    @Override
-    protected Bitmap onGetBitmap() {
-        Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, mConfig);
-        mCanvas = new Canvas(bitmap);
-        onDraw(mCanvas, bitmap);
-        return bitmap;
-    }
-
-    @Override
-    protected void onFreeBitmap(Bitmap bitmap) {
-        if (!inFinalizer()) {
-            bitmap.recycle();
-        }
-    }
-
-    abstract protected void onDraw(Canvas canvas, Bitmap backing);
-}
diff --git a/src/com/android/gallery3d/ui/ColorTexture.java b/src/com/android/gallery3d/ui/ColorTexture.java
deleted file mode 100644
index 733c056..0000000
--- a/src/com/android/gallery3d/ui/ColorTexture.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import com.android.gallery3d.common.Utils;
-
-// ColorTexture is a texture which fills the rectangle with the specified color.
-public class ColorTexture implements Texture {
-
-    private final int mColor;
-    private int mWidth;
-    private int mHeight;
-
-    public ColorTexture(int color) {
-        mColor = color;
-        mWidth = 1;
-        mHeight = 1;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y) {
-        draw(canvas, x, y, mWidth, mHeight);
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
-        canvas.fillRect(x, y, w, h, mColor);
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return Utils.isOpaque(mColor);
-    }
-
-    public void setSize(int width, int height) {
-        mWidth = width;
-        mHeight = height;
-    }
-
-    @Override
-    public int getWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        return mHeight;
-    }
-}
diff --git a/src/com/android/gallery3d/ui/CropView.java b/src/com/android/gallery3d/ui/CropView.java
deleted file mode 100644
index 1890c76..0000000
--- a/src/com/android/gallery3d/ui/CropView.java
+++ /dev/null
@@ -1,801 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.media.FaceDetector;
-import android.os.Handler;
-import android.os.Message;
-import android.util.FloatMath;
-import android.view.MotionEvent;
-import android.view.animation.DecelerateInterpolator;
-import android.widget.Toast;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.anim.Animation;
-import com.android.gallery3d.app.AbstractGalleryActivity;
-import com.android.gallery3d.common.Utils;
-
-import java.util.ArrayList;
-
-import javax.microedition.khronos.opengles.GL11;
-
-/**
- * The activity can crop specific region of interest from an image.
- */
-public class CropView extends GLView {
-    @SuppressWarnings("unused")
-    private static final String TAG = "CropView";
-
-    private static final int FACE_PIXEL_COUNT = 120000; // around 400x300
-
-    private static final int COLOR_OUTLINE = 0xFF008AFF;
-    private static final int COLOR_FACE_OUTLINE = 0xFF000000;
-
-    private static final float OUTLINE_WIDTH = 3f;
-
-    private static final int SIZE_UNKNOWN = -1;
-    private static final int TOUCH_TOLERANCE = 30;
-
-    private static final float MIN_SELECTION_LENGTH = 16f;
-    public static final float UNSPECIFIED = -1f;
-
-    private static final int MAX_FACE_COUNT = 3;
-    private static final float FACE_EYE_RATIO = 2f;
-
-    private static final int ANIMATION_DURATION = 1250;
-
-    private static final int MOVE_LEFT = 1;
-    private static final int MOVE_TOP = 2;
-    private static final int MOVE_RIGHT = 4;
-    private static final int MOVE_BOTTOM = 8;
-    private static final int MOVE_BLOCK = 16;
-
-    private static final float MAX_SELECTION_RATIO = 0.8f;
-    private static final float MIN_SELECTION_RATIO = 0.4f;
-    private static final float SELECTION_RATIO = 0.60f;
-    private static final int ANIMATION_TRIGGER = 64;
-
-    private static final int MSG_UPDATE_FACES = 1;
-
-    private float mAspectRatio = UNSPECIFIED;
-    private float mSpotlightRatioX = 0;
-    private float mSpotlightRatioY = 0;
-
-    private Handler mMainHandler;
-
-    private FaceHighlightView mFaceDetectionView;
-    private HighlightRectangle mHighlightRectangle;
-    private TileImageView mImageView;
-    private AnimationController mAnimation = new AnimationController();
-
-    private int mImageWidth = SIZE_UNKNOWN;
-    private int mImageHeight = SIZE_UNKNOWN;
-
-    private AbstractGalleryActivity mActivity;
-
-    private GLPaint mPaint = new GLPaint();
-    private GLPaint mFacePaint = new GLPaint();
-
-    private int mImageRotation;
-
-    public CropView(AbstractGalleryActivity activity) {
-        mActivity = activity;
-        mImageView = new TileImageView(activity);
-        mFaceDetectionView = new FaceHighlightView();
-        mHighlightRectangle = new HighlightRectangle();
-
-        addComponent(mImageView);
-        addComponent(mFaceDetectionView);
-        addComponent(mHighlightRectangle);
-
-        mHighlightRectangle.setVisibility(GLView.INVISIBLE);
-
-        mPaint.setColor(COLOR_OUTLINE);
-        mPaint.setLineWidth(OUTLINE_WIDTH);
-
-        mFacePaint.setColor(COLOR_FACE_OUTLINE);
-        mFacePaint.setLineWidth(OUTLINE_WIDTH);
-
-        mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
-            @Override
-            public void handleMessage(Message message) {
-                Utils.assertTrue(message.what == MSG_UPDATE_FACES);
-                ((DetectFaceTask) message.obj).updateFaces();
-            }
-        };
-    }
-
-    public void setAspectRatio(float ratio) {
-        mAspectRatio = ratio;
-    }
-
-    public void setSpotlightRatio(float ratioX, float ratioY) {
-        mSpotlightRatioX = ratioX;
-        mSpotlightRatioY = ratioY;
-    }
-
-    @Override
-    public void onLayout(boolean changed, int l, int t, int r, int b) {
-        int width = r - l;
-        int height = b - t;
-
-        mFaceDetectionView.layout(0, 0, width, height);
-        mHighlightRectangle.layout(0, 0, width, height);
-        mImageView.layout(0, 0, width, height);
-        if (mImageHeight != SIZE_UNKNOWN) {
-            mAnimation.initialize();
-            if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) {
-                mAnimation.parkNow(
-                        mHighlightRectangle.mHighlightRect);
-            }
-        }
-    }
-
-    private boolean setImageViewPosition(int centerX, int centerY, float scale) {
-        int inverseX = mImageWidth - centerX;
-        int inverseY = mImageHeight - centerY;
-        TileImageView t = mImageView;
-        int rotation = mImageRotation;
-        switch (rotation) {
-            case 0: return t.setPosition(centerX, centerY, scale, 0);
-            case 90: return t.setPosition(centerY, inverseX, scale, 90);
-            case 180: return t.setPosition(inverseX, inverseY, scale, 180);
-            case 270: return t.setPosition(inverseY, centerX, scale, 270);
-            default: throw new IllegalArgumentException(String.valueOf(rotation));
-        }
-    }
-
-    @Override
-    public void render(GLCanvas canvas) {
-        AnimationController a = mAnimation;
-        if (a.calculate(AnimationTime.get())) invalidate();
-        setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale());
-        super.render(canvas);
-    }
-
-    @Override
-    public void renderBackground(GLCanvas canvas) {
-        canvas.clearBuffer();
-    }
-
-    public RectF getCropRectangle() {
-        if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null;
-        RectF rect = mHighlightRectangle.mHighlightRect;
-        RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight,
-                rect.right * mImageWidth, rect.bottom * mImageHeight);
-        return result;
-    }
-
-    public int getImageWidth() {
-        return mImageWidth;
-    }
-
-    public int getImageHeight() {
-        return mImageHeight;
-    }
-
-    private class FaceHighlightView extends GLView {
-        private static final int INDEX_NONE = -1;
-        private ArrayList<RectF> mFaces = new ArrayList<RectF>();
-        private RectF mRect = new RectF();
-        private int mPressedFaceIndex = INDEX_NONE;
-
-        public void addFace(RectF faceRect) {
-            mFaces.add(faceRect);
-            invalidate();
-        }
-
-        private void renderFace(GLCanvas canvas, RectF face, boolean pressed) {
-            GL11 gl = canvas.getGLInstance();
-            if (pressed) {
-                gl.glEnable(GL11.GL_STENCIL_TEST);
-                gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
-                gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
-                gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
-            }
-
-            RectF r = mAnimation.mapRect(face, mRect);
-            canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
-            canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint);
-
-            if (pressed) {
-                gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
-            }
-        }
-
-        @Override
-        protected void renderBackground(GLCanvas canvas) {
-            ArrayList<RectF> faces = mFaces;
-            for (int i = 0, n = faces.size(); i < n; ++i) {
-                renderFace(canvas, faces.get(i), i == mPressedFaceIndex);
-            }
-
-            GL11 gl = canvas.getGLInstance();
-            if (mPressedFaceIndex != INDEX_NONE) {
-                gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
-                canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000);
-                gl.glDisable(GL11.GL_STENCIL_TEST);
-            }
-        }
-
-        private void setPressedFace(int index) {
-            if (mPressedFaceIndex == index) return;
-            mPressedFaceIndex = index;
-            invalidate();
-        }
-
-        private int getFaceIndexByPosition(float x, float y) {
-            ArrayList<RectF> faces = mFaces;
-            for (int i = 0, n = faces.size(); i < n; ++i) {
-                RectF r = mAnimation.mapRect(faces.get(i), mRect);
-                if (r.contains(x, y)) return i;
-            }
-            return INDEX_NONE;
-        }
-
-        @Override
-        protected boolean onTouch(MotionEvent event) {
-            float x = event.getX();
-            float y = event.getY();
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_MOVE: {
-                    setPressedFace(getFaceIndexByPosition(x, y));
-                    break;
-                }
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP: {
-                    int index = mPressedFaceIndex;
-                    setPressedFace(INDEX_NONE);
-                    if (index != INDEX_NONE) {
-                        mHighlightRectangle.setRectangle(mFaces.get(index));
-                        mHighlightRectangle.setVisibility(GLView.VISIBLE);
-                        setVisibility(GLView.INVISIBLE);
-                    }
-                }
-            }
-            return true;
-        }
-    }
-
-    private class AnimationController extends Animation {
-        private int mCurrentX;
-        private int mCurrentY;
-        private float mCurrentScale;
-        private int mStartX;
-        private int mStartY;
-        private float mStartScale;
-        private int mTargetX;
-        private int mTargetY;
-        private float mTargetScale;
-
-        public AnimationController() {
-            setDuration(ANIMATION_DURATION);
-            setInterpolator(new DecelerateInterpolator(4));
-        }
-
-        public void initialize() {
-            mCurrentX = mImageWidth / 2;
-            mCurrentY = mImageHeight / 2;
-            mCurrentScale = Math.min(2, Math.min(
-                    (float) getWidth() / mImageWidth,
-                    (float) getHeight() / mImageHeight));
-        }
-
-        public void startParkingAnimation(RectF highlight) {
-            RectF r = mAnimation.mapRect(highlight, new RectF());
-            int width = getWidth();
-            int height = getHeight();
-
-            float wr = r.width() / width;
-            float hr = r.height() / height;
-            final int d = ANIMATION_TRIGGER;
-            if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO
-                    && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO
-                    && r.left >= d && r.right < width - d
-                    && r.top >= d && r.bottom < height - d) return;
-
-            mStartX = mCurrentX;
-            mStartY = mCurrentY;
-            mStartScale = mCurrentScale;
-            calculateTarget(highlight);
-            start();
-        }
-
-        public void parkNow(RectF highlight) {
-            calculateTarget(highlight);
-            forceStop();
-            mStartX = mCurrentX = mTargetX;
-            mStartY = mCurrentY = mTargetY;
-            mStartScale = mCurrentScale = mTargetScale;
-        }
-
-        public void inverseMapPoint(PointF point) {
-            float s = mCurrentScale;
-            point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s
-                    + mCurrentX) / mImageWidth, 0, 1);
-            point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s
-                    + mCurrentY) / mImageHeight, 0, 1);
-        }
-
-        public RectF mapRect(RectF input, RectF output) {
-            float offsetX = getWidth() * 0.5f;
-            float offsetY = getHeight() * 0.5f;
-            int x = mCurrentX;
-            int y = mCurrentY;
-            float s = mCurrentScale;
-            output.set(
-                    offsetX + (input.left * mImageWidth - x) * s,
-                    offsetY + (input.top * mImageHeight - y) * s,
-                    offsetX + (input.right * mImageWidth - x) * s,
-                    offsetY + (input.bottom * mImageHeight - y) * s);
-            return output;
-        }
-
-        @Override
-        protected void onCalculate(float progress) {
-            mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress);
-            mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress);
-            mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress;
-
-            if (mCurrentX == mTargetX && mCurrentY == mTargetY
-                    && mCurrentScale == mTargetScale) forceStop();
-        }
-
-        public int getCenterX() {
-            return mCurrentX;
-        }
-
-        public int getCenterY() {
-            return mCurrentY;
-        }
-
-        public float getScale() {
-            return mCurrentScale;
-        }
-
-        private void calculateTarget(RectF highlight) {
-            float width = getWidth();
-            float height = getHeight();
-
-            if (mImageWidth != SIZE_UNKNOWN) {
-                float minScale = Math.min(width / mImageWidth, height / mImageHeight);
-                float scale = Utils.clamp(SELECTION_RATIO * Math.min(
-                        width / (highlight.width() * mImageWidth),
-                        height / (highlight.height() * mImageHeight)), minScale, 2f);
-                int centerX = Math.round(
-                        mImageWidth * (highlight.left + highlight.right) * 0.5f);
-                int centerY = Math.round(
-                        mImageHeight * (highlight.top + highlight.bottom) * 0.5f);
-
-                if (Math.round(mImageWidth * scale) > width) {
-                    int limitX = Math.round(width * 0.5f / scale);
-                    centerX = Math.round(
-                            (highlight.left + highlight.right) * mImageWidth / 2);
-                    centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX);
-                } else {
-                    centerX = mImageWidth / 2;
-                }
-                if (Math.round(mImageHeight * scale) > height) {
-                    int limitY = Math.round(height * 0.5f / scale);
-                    centerY = Math.round(
-                            (highlight.top + highlight.bottom) * mImageHeight / 2);
-                    centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY);
-                } else {
-                    centerY = mImageHeight / 2;
-                }
-                mTargetX = centerX;
-                mTargetY = centerY;
-                mTargetScale = scale;
-            }
-        }
-
-    }
-
-    private class HighlightRectangle extends GLView {
-        private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f);
-        private RectF mTempRect = new RectF();
-        private PointF mTempPoint = new PointF();
-
-        private ResourceTexture mArrow;
-
-        private int mMovingEdges = 0;
-        private float mReferenceX;
-        private float mReferenceY;
-
-        public HighlightRectangle() {
-            mArrow = new ResourceTexture(mActivity.getAndroidContext(),
-                    R.drawable.camera_crop_holo);
-        }
-
-        public void setInitRectangle() {
-            float targetRatio = mAspectRatio == UNSPECIFIED
-                    ? 1f
-                    : mAspectRatio * mImageHeight / mImageWidth;
-            float w = SELECTION_RATIO / 2f;
-            float h = SELECTION_RATIO / 2f;
-            if (targetRatio > 1) {
-                h = w / targetRatio;
-            } else {
-                w = h * targetRatio;
-            }
-            mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h);
-        }
-
-        public void setRectangle(RectF faceRect) {
-            mHighlightRect.set(faceRect);
-            mAnimation.startParkingAnimation(faceRect);
-            invalidate();
-        }
-
-        private void moveEdges(MotionEvent event) {
-            float scale = mAnimation.getScale();
-            float dx = (event.getX() - mReferenceX) / scale / mImageWidth;
-            float dy = (event.getY() - mReferenceY) / scale / mImageHeight;
-            mReferenceX = event.getX();
-            mReferenceY = event.getY();
-            RectF r = mHighlightRect;
-
-            if ((mMovingEdges & MOVE_BLOCK) != 0) {
-                dx = Utils.clamp(dx, -r.left,  1 - r.right);
-                dy = Utils.clamp(dy, -r.top , 1 - r.bottom);
-                r.top += dy;
-                r.bottom += dy;
-                r.left += dx;
-                r.right += dx;
-            } else {
-                PointF point = mTempPoint;
-                point.set(mReferenceX, mReferenceY);
-                mAnimation.inverseMapPoint(point);
-                float left = r.left + MIN_SELECTION_LENGTH / mImageWidth;
-                float right = r.right - MIN_SELECTION_LENGTH / mImageWidth;
-                float top = r.top + MIN_SELECTION_LENGTH / mImageHeight;
-                float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight;
-                if ((mMovingEdges & MOVE_RIGHT) != 0) {
-                    r.right = Utils.clamp(point.x, left, 1f);
-                }
-                if ((mMovingEdges & MOVE_LEFT) != 0) {
-                    r.left = Utils.clamp(point.x, 0, right);
-                }
-                if ((mMovingEdges & MOVE_TOP) != 0) {
-                    r.top = Utils.clamp(point.y, 0, bottom);
-                }
-                if ((mMovingEdges & MOVE_BOTTOM) != 0) {
-                    r.bottom = Utils.clamp(point.y, top, 1f);
-                }
-                if (mAspectRatio != UNSPECIFIED) {
-                    float targetRatio = mAspectRatio * mImageHeight / mImageWidth;
-                    if (r.width() / r.height() > targetRatio) {
-                        float height = r.width() / targetRatio;
-                        if ((mMovingEdges & MOVE_BOTTOM) != 0) {
-                            r.bottom = Utils.clamp(r.top + height, top, 1f);
-                        } else {
-                            r.top = Utils.clamp(r.bottom - height, 0, bottom);
-                        }
-                    } else {
-                        float width = r.height() * targetRatio;
-                        if ((mMovingEdges & MOVE_LEFT) != 0) {
-                            r.left = Utils.clamp(r.right - width, 0, right);
-                        } else {
-                            r.right = Utils.clamp(r.left + width, left, 1f);
-                        }
-                    }
-                    if (r.width() / r.height() > targetRatio) {
-                        float width = r.height() * targetRatio;
-                        if ((mMovingEdges & MOVE_LEFT) != 0) {
-                            r.left = Utils.clamp(r.right - width, 0, right);
-                        } else {
-                            r.right = Utils.clamp(r.left + width, left, 1f);
-                        }
-                    } else {
-                        float height = r.width() / targetRatio;
-                        if ((mMovingEdges & MOVE_BOTTOM) != 0) {
-                            r.bottom = Utils.clamp(r.top + height, top, 1f);
-                        } else {
-                            r.top = Utils.clamp(r.bottom - height, 0, bottom);
-                        }
-                    }
-                }
-            }
-            invalidate();
-        }
-
-        private void setMovingEdges(MotionEvent event) {
-            RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
-            float x = event.getX();
-            float y = event.getY();
-
-            if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE
-                    && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) {
-                mMovingEdges = MOVE_BLOCK;
-                return;
-            }
-
-            boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y
-                    && y <= (r.bottom + TOUCH_TOLERANCE);
-            boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x
-                    && x <= (r.right + TOUCH_TOLERANCE);
-
-            if (inVerticalRange) {
-                boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE;
-                boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE;
-                if (left && right) {
-                    left = Math.abs(x - r.left) < Math.abs(x - r.right);
-                    right = !left;
-                }
-                if (left) mMovingEdges |= MOVE_LEFT;
-                if (right) mMovingEdges |= MOVE_RIGHT;
-                if (mAspectRatio != UNSPECIFIED && inHorizontalRange) {
-                    mMovingEdges |= (y >
-                            (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP;
-                }
-            }
-            if (inHorizontalRange) {
-                boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE;
-                boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE;
-                if (top && bottom) {
-                    top = Math.abs(y - r.top) < Math.abs(y - r.bottom);
-                    bottom = !top;
-                }
-                if (top) mMovingEdges |= MOVE_TOP;
-                if (bottom) mMovingEdges |= MOVE_BOTTOM;
-                if (mAspectRatio != UNSPECIFIED && inVerticalRange) {
-                    mMovingEdges |= (x >
-                            (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT;
-                }
-            }
-        }
-
-        @Override
-        protected boolean onTouch(MotionEvent event) {
-            switch (event.getAction()) {
-                case MotionEvent.ACTION_DOWN: {
-                    mReferenceX = event.getX();
-                    mReferenceY = event.getY();
-                    setMovingEdges(event);
-                    invalidate();
-                    return true;
-                }
-                case MotionEvent.ACTION_MOVE:
-                    moveEdges(event);
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP: {
-                    mMovingEdges = 0;
-                    mAnimation.startParkingAnimation(mHighlightRect);
-                    invalidate();
-                    return true;
-                }
-            }
-            return true;
-        }
-
-        @Override
-        protected void renderBackground(GLCanvas canvas) {
-            RectF r = mAnimation.mapRect(mHighlightRect, mTempRect);
-            drawHighlightRectangle(canvas, r);
-
-            float centerY = (r.top + r.bottom) / 2;
-            float centerX = (r.left + r.right) / 2;
-            boolean notMoving = mMovingEdges == 0;
-            if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) {
-                mArrow.draw(canvas,
-                        Math.round(r.right - mArrow.getWidth() / 2),
-                        Math.round(centerY - mArrow.getHeight() / 2));
-            }
-            if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) {
-                mArrow.draw(canvas,
-                        Math.round(r.left - mArrow.getWidth() / 2),
-                        Math.round(centerY - mArrow.getHeight() / 2));
-            }
-            if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) {
-                mArrow.draw(canvas,
-                        Math.round(centerX - mArrow.getWidth() / 2),
-                        Math.round(r.top - mArrow.getHeight() / 2));
-            }
-            if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) {
-                mArrow.draw(canvas,
-                        Math.round(centerX - mArrow.getWidth() / 2),
-                        Math.round(r.bottom - mArrow.getHeight() / 2));
-            }
-        }
-
-        private void drawHighlightRectangle(GLCanvas canvas, RectF r) {
-            GL11 gl = canvas.getGLInstance();
-            gl.glLineWidth(3.0f);
-            gl.glEnable(GL11.GL_LINE_SMOOTH);
-
-            gl.glEnable(GL11.GL_STENCIL_TEST);
-            gl.glClear(GL11.GL_STENCIL_BUFFER_BIT);
-            gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
-            gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
-
-            if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) {
-                canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT);
-                canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
-            } else {
-                float sx = r.width() * mSpotlightRatioX;
-                float sy = r.height() * mSpotlightRatioY;
-                float cx = r.centerX();
-                float cy = r.centerY();
-
-                canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT);
-                canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint);
-                canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint);
-
-                gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
-                gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
-
-                canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint);
-                canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT);
-                canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000);
-            }
-
-            gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1);
-            gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
-
-            canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000);
-
-            gl.glDisable(GL11.GL_STENCIL_TEST);
-        }
-    }
-
-    private class DetectFaceTask extends Thread {
-        private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT];
-        private final Bitmap mFaceBitmap;
-        private int mFaceCount;
-
-        public DetectFaceTask(Bitmap bitmap) {
-            mFaceBitmap = bitmap;
-            setName("face-detect");
-        }
-
-        @Override
-        public void run() {
-            Bitmap bitmap = mFaceBitmap;
-            FaceDetector detector = new FaceDetector(
-                    bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT);
-            mFaceCount = detector.findFaces(bitmap, mFaces);
-            mMainHandler.sendMessage(
-                    mMainHandler.obtainMessage(MSG_UPDATE_FACES, this));
-        }
-
-        private RectF getFaceRect(FaceDetector.Face face) {
-            PointF point = new PointF();
-            face.getMidPoint(point);
-
-            int width = mFaceBitmap.getWidth();
-            int height = mFaceBitmap.getHeight();
-            float rx = face.eyesDistance() * FACE_EYE_RATIO;
-            float ry = rx;
-            float aspect = mAspectRatio;
-            if (aspect != UNSPECIFIED) {
-                if (aspect > 1) {
-                    rx = ry * aspect;
-                } else {
-                    ry = rx / aspect;
-                }
-            }
-
-            RectF r = new RectF(
-                    point.x - rx, point.y - ry, point.x + rx, point.y + ry);
-            r.intersect(0, 0, width, height);
-
-            if (aspect != UNSPECIFIED) {
-                if (r.width() / r.height() > aspect) {
-                    float w = r.height() * aspect;
-                    r.left = (r.left + r.right - w) * 0.5f;
-                    r.right = r.left + w;
-                } else {
-                    float h = r.width() / aspect;
-                    r.top =  (r.top + r.bottom - h) * 0.5f;
-                    r.bottom = r.top + h;
-                }
-            }
-
-            r.left /= width;
-            r.right /= width;
-            r.top /= height;
-            r.bottom /= height;
-            return r;
-        }
-
-        public void updateFaces() {
-            if (mFaceCount > 1) {
-                for (int i = 0, n = mFaceCount; i < n; ++i) {
-                    mFaceDetectionView.addFace(getFaceRect(mFaces[i]));
-                }
-                mFaceDetectionView.setVisibility(GLView.VISIBLE);
-                Toast.makeText(mActivity.getAndroidContext(),
-                        R.string.multiface_crop_help, Toast.LENGTH_SHORT).show();
-            } else if (mFaceCount == 1) {
-                mFaceDetectionView.setVisibility(GLView.INVISIBLE);
-                mHighlightRectangle.setRectangle(getFaceRect(mFaces[0]));
-                mHighlightRectangle.setVisibility(GLView.VISIBLE);
-            } else /*mFaceCount == 0*/ {
-                mHighlightRectangle.setInitRectangle();
-                mHighlightRectangle.setVisibility(GLView.VISIBLE);
-            }
-        }
-    }
-
-    public void setDataModel(TileImageView.Model dataModel, int rotation) {
-        if (((rotation / 90) & 0x01) != 0) {
-            mImageWidth = dataModel.getImageHeight();
-            mImageHeight = dataModel.getImageWidth();
-        } else {
-            mImageWidth = dataModel.getImageWidth();
-            mImageHeight = dataModel.getImageHeight();
-        }
-
-        mImageRotation = rotation;
-
-        mImageView.setModel(dataModel);
-        mAnimation.initialize();
-    }
-
-    public void detectFaces(Bitmap bitmap) {
-        int rotation = mImageRotation;
-        int width = bitmap.getWidth();
-        int height = bitmap.getHeight();
-        float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height));
-
-        // faceBitmap is a correctly rotated bitmap, as viewed by a user.
-        Bitmap faceBitmap;
-        if (((rotation / 90) & 1) == 0) {
-            int w = (Math.round(width * scale) & ~1); // must be even
-            int h = Math.round(height * scale);
-            faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
-            Canvas canvas = new Canvas(faceBitmap);
-            canvas.rotate(rotation, w / 2, h / 2);
-            canvas.scale((float) w / width, (float) h / height);
-            canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
-        } else {
-            int w = (Math.round(height * scale) & ~1); // must be even
-            int h = Math.round(width * scale);
-            faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565);
-            Canvas canvas = new Canvas(faceBitmap);
-            canvas.translate(w / 2, h / 2);
-            canvas.rotate(rotation);
-            canvas.translate(-h / 2, -w / 2);
-            canvas.scale((float) w / height, (float) h / width);
-            canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG));
-        }
-        new DetectFaceTask(faceBitmap).start();
-    }
-
-    public void initializeHighlightRectangle() {
-        mHighlightRectangle.setInitRectangle();
-        mHighlightRectangle.setVisibility(GLView.VISIBLE);
-    }
-
-    public void resume() {
-        mImageView.prepareTextures();
-    }
-
-    public void pause() {
-        mImageView.freeTextures();
-    }
-}
-
diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java
index 3016011..47296f6 100644
--- a/src/com/android/gallery3d/ui/DetailsHelper.java
+++ b/src/com/android/gallery3d/ui/DetailsHelper.java
@@ -16,6 +16,8 @@
 package com.android.gallery3d.ui;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.view.View.MeasureSpec;
 
 import com.android.gallery3d.R;
@@ -44,6 +46,10 @@
         public void hide();
     }
 
+    public interface ResolutionResolvingListener {
+        public void onResolutionAvailable(int width, int height);
+    }
+
     public DetailsHelper(AbstractGalleryActivity activity, GLView rootPane, DetailsSource source) {
         mContainer = new DialogDetailsView(activity, source);
     }
@@ -75,6 +81,12 @@
         return sAddressResolver.resolveAddress(latlng, listener);
     }
 
+    public static void resolveResolution(String path, ResolutionResolvingListener listener) {
+        Bitmap bitmap = BitmapFactory.decodeFile(path);
+        if (bitmap == null) return;
+        listener.onResolutionAvailable(bitmap.getWidth(), bitmap.getHeight());
+    }
+
     public static void pause() {
         if (sAddressResolver != null) sAddressResolver.cancel();
     }
diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java
index 8d96b82..058c036 100644
--- a/src/com/android/gallery3d/ui/DialogDetailsView.java
+++ b/src/com/android/gallery3d/ui/DialogDetailsView.java
@@ -37,6 +37,7 @@
 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
 import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer;
+import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener;
 
 import java.util.ArrayList;
 import java.util.Map.Entry;
@@ -111,9 +112,13 @@
         });
     }
 
-    private class DetailsAdapter extends BaseAdapter implements AddressResolvingListener {
+
+    private class DetailsAdapter extends BaseAdapter
+        implements AddressResolvingListener, ResolutionResolvingListener {
         private final ArrayList<String> mItems;
         private int mLocationIndex;
+        private int mWidthIndex = -1;
+        private int mHeightIndex = -1;
 
         public DetailsAdapter(MediaDetails details) {
             Context context = mActivity.getAndroidContext();
@@ -123,6 +128,8 @@
         }
 
         private void setDetails(Context context, MediaDetails details) {
+            boolean resolutionIsValid = true;
+            String path = null;
             for (Entry<Integer, Object> detail : details) {
                 String value;
                 switch (detail.getKey()) {
@@ -170,6 +177,26 @@
                         }
                         break;
                     }
+                    case MediaDetails.INDEX_WIDTH:
+                        mWidthIndex = mItems.size();
+                        value = detail.getValue().toString();
+                        if (value.equalsIgnoreCase("0")) {
+                            value = context.getString(R.string.unknown);
+                            resolutionIsValid = false;
+                        }
+                        break;
+                    case MediaDetails.INDEX_HEIGHT: {
+                        mHeightIndex = mItems.size();
+                        value = detail.getValue().toString();
+                        if (value.equalsIgnoreCase("0")) {
+                            value = context.getString(R.string.unknown);
+                            resolutionIsValid = false;
+                        }
+                        break;
+                    }
+                    case MediaDetails.INDEX_PATH:
+                        // Get the path and then fall through to the default case
+                        path = detail.getValue().toString();
                     default: {
                         Object valueObj = detail.getValue();
                         // This shouldn't happen, log its key to help us diagnose the problem.
@@ -189,6 +216,9 @@
                             context, key), value);
                 }
                 mItems.add(value);
+                if (!resolutionIsValid) {
+                    DetailsHelper.resolveResolution(path, this);
+                }
             }
         }
 
@@ -235,6 +265,20 @@
             mItems.set(mLocationIndex, address);
             notifyDataSetChanged();
         }
+
+        @Override
+        public void onResolutionAvailable(int width, int height) {
+            if (width == 0 || height == 0) return;
+            // Update the resolution with the new width and height
+            Context context = mActivity.getAndroidContext();
+            String widthString = String.format("%s: %d", DetailsHelper.getDetailsName(
+                    context, MediaDetails.INDEX_WIDTH), width);
+            String heightString = String.format("%s: %d", DetailsHelper.getDetailsName(
+                    context, MediaDetails.INDEX_HEIGHT), height);
+            mItems.set(mWidthIndex, String.valueOf(widthString));
+            mItems.set(mHeightIndex, String.valueOf(heightString));
+            notifyDataSetChanged();
+        }
     }
 
     @Override
diff --git a/src/com/android/gallery3d/ui/EdgeEffect.java b/src/com/android/gallery3d/ui/EdgeEffect.java
index ed36973..87ff0c5 100644
--- a/src/com/android/gallery3d/ui/EdgeEffect.java
+++ b/src/com/android/gallery3d/ui/EdgeEffect.java
@@ -23,6 +23,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.ResourceTexture;
 
 // This is copied from android.widget.EdgeEffect with some small modifications:
 // (1) Copy the images (overscroll_{edge|glow}.png) to local resources.
diff --git a/src/com/android/gallery3d/ui/EdgeView.java b/src/com/android/gallery3d/ui/EdgeView.java
index 4aff049..051de18 100644
--- a/src/com/android/gallery3d/ui/EdgeView.java
+++ b/src/com/android/gallery3d/ui/EdgeView.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.opengl.Matrix;
 
+import com.android.gallery3d.glrenderer.GLCanvas;
+
 // EdgeView draws EdgeEffect (blue glow) at four sides of the view.
 public class EdgeView extends GLView {
     @SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/ui/ExtTexture.java b/src/com/android/gallery3d/ui/ExtTexture.java
deleted file mode 100644
index eac504f..0000000
--- a/src/com/android/gallery3d/ui/ExtTexture.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11Ext;
-
-// ExtTexture is a texture whose content comes from a external texture.
-// Before drawing, setSize() should be called.
-public class ExtTexture extends BasicTexture {
-
-    private static int[] sTextureId = new int[1];
-    private static float[] sCropRect = new float[4];
-    private int mTarget;
-
-    public ExtTexture(int target) {
-        GLId.glGenTextures(1, sTextureId, 0);
-        mId = sTextureId[0];
-        mTarget = target;
-    }
-
-    private void uploadToCanvas(GLCanvas canvas) {
-        GL11 gl = canvas.getGLInstance();
-
-        int width = getWidth();
-        int height = getHeight();
-        // Define a vertically flipped crop rectangle for OES_draw_texture.
-        // The four values in sCropRect are: left, bottom, width, and
-        // height. Negative value of width or height means flip.
-        sCropRect[0] = 0;
-        sCropRect[1] = height;
-        sCropRect[2] = width;
-        sCropRect[3] = -height;
-
-        // Set texture parameters.
-        gl.glBindTexture(mTarget, mId);
-        gl.glTexParameterfv(mTarget,
-                GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
-        gl.glTexParameteri(mTarget,
-                GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
-        gl.glTexParameteri(mTarget,
-                GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
-        gl.glTexParameterf(mTarget,
-                GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
-        gl.glTexParameterf(mTarget,
-                GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
-
-        setAssociatedCanvas(canvas);
-        mState = STATE_LOADED;
-    }
-
-    @Override
-    protected boolean onBind(GLCanvas canvas) {
-        if (!isLoaded()) {
-            uploadToCanvas(canvas);
-        }
-
-        return true;
-    }
-
-    @Override
-    public int getTarget() {
-        return mTarget;
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return true;
-    }
-
-    @Override
-    public void yield() {
-        // we cannot free the texture because we have no backup.
-    }
-}
diff --git a/src/com/android/gallery3d/ui/FadeInTexture.java b/src/com/android/gallery3d/ui/FadeInTexture.java
deleted file mode 100644
index c6a9811..0000000
--- a/src/com/android/gallery3d/ui/FadeInTexture.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.ui;
-
-// FadeInTexture is a texture which begins with a color, then gradually animates
-// into a given texture.
-public class FadeInTexture extends FadeTexture implements Texture {
-    @SuppressWarnings("unused")
-    private static final String TAG = "FadeInTexture";
-
-    private final int mColor;
-    private final TiledTexture mTexture;
-
-    public FadeInTexture(int color, TiledTexture texture) {
-        super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
-        mColor = color;
-        mTexture = texture;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
-        if (isAnimating()) {
-            mTexture.drawMixed(canvas, mColor, getRatio(), x, y, w, h);
-        } else {
-            mTexture.draw(canvas, x, y, w, h);
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/FadeOutTexture.java b/src/com/android/gallery3d/ui/FadeOutTexture.java
deleted file mode 100644
index 7050e53..0000000
--- a/src/com/android/gallery3d/ui/FadeOutTexture.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.ui;
-
-// FadeOutTexture is a texture which begins with a given texture, then gradually animates
-// into fading out totally.
-public class FadeOutTexture extends FadeTexture {
-    @SuppressWarnings("unused")
-    private static final String TAG = "FadeOutTexture";
-
-    private final BasicTexture mTexture;
-
-    public FadeOutTexture(BasicTexture texture) {
-        super(texture.getWidth(), texture.getHeight(), texture.isOpaque());
-        mTexture = texture;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
-        if (isAnimating()) {
-            canvas.save(GLCanvas.SAVE_FLAG_ALPHA);
-            canvas.setAlpha(getRatio());
-            mTexture.draw(canvas, x, y, w, h);
-            canvas.restore();
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/FadeTexture.java b/src/com/android/gallery3d/ui/FadeTexture.java
deleted file mode 100644
index 5236d36..0000000
--- a/src/com/android/gallery3d/ui/FadeTexture.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.ui;
-
-import com.android.gallery3d.common.Utils;
-
-// FadeTexture is a texture which fades the given texture along the time.
-public abstract class FadeTexture implements Texture {
-    @SuppressWarnings("unused")
-    private static final String TAG = "FadeTexture";
-
-    // The duration of the fading animation in milliseconds
-    public static final int DURATION = 180;
-
-    private final long mStartTime;
-    private final int mWidth;
-    private final int mHeight;
-    private final boolean mIsOpaque;
-    private boolean mIsAnimating;
-
-    public FadeTexture(int width, int height, boolean opaque) {
-        mWidth = width;
-        mHeight = height;
-        mIsOpaque = opaque;
-        mStartTime = now();
-        mIsAnimating = true;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y) {
-        draw(canvas, x, y, mWidth, mHeight);
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return mIsOpaque;
-    }
-
-    @Override
-    public int getWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        return mHeight;
-    }
-
-    public boolean isAnimating() {
-        if (mIsAnimating) {
-            if (now() - mStartTime >= DURATION) {
-                mIsAnimating = false;
-            }
-        }
-        return mIsAnimating;
-    }
-
-    protected float getRatio() {
-        float r = (float)(now() - mStartTime) / DURATION;
-        return Utils.clamp(1.0f - r, 0.0f, 1.0f);
-    }
-
-    private long now() {
-        return AnimationTime.get();
-    }
-}
diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java
deleted file mode 100644
index 6f8baef..0000000
--- a/src/com/android/gallery3d/ui/GLCanvas.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.RectF;
-
-import javax.microedition.khronos.opengles.GL11;
-
-//
-// GLCanvas gives a convenient interface to draw using OpenGL.
-//
-// When a rectangle is specified in this interface, it means the region
-// [x, x+width) * [y, y+height)
-//
-public interface GLCanvas {
-    // Tells GLCanvas the size of the underlying GL surface. This should be
-    // called before first drawing and when the size of GL surface is changed.
-    // This is called by GLRoot and should not be called by the clients
-    // who only want to draw on the GLCanvas. Both width and height must be
-    // nonnegative.
-    public void setSize(int width, int height);
-
-    // Clear the drawing buffers. This should only be used by GLRoot.
-    public void clearBuffer();
-    public void clearBuffer(float[] argb);
-
-    // Sets and gets the current alpha, alpha must be in [0, 1].
-    public void setAlpha(float alpha);
-    public float getAlpha();
-
-    // (current alpha) = (current alpha) * alpha
-    public void multiplyAlpha(float alpha);
-
-    // Change the current transform matrix.
-    public void translate(float x, float y, float z);
-    public void translate(float x, float y);
-    public void scale(float sx, float sy, float sz);
-    public void rotate(float angle, float x, float y, float z);
-    public void multiplyMatrix(float[] mMatrix, int offset);
-
-    // Pushes the configuration state (matrix, and alpha) onto
-    // a private stack.
-    public void save();
-
-    // Same as save(), but only save those specified in saveFlags.
-    public void save(int saveFlags);
-
-    public static final int SAVE_FLAG_ALL = 0xFFFFFFFF;
-    public static final int SAVE_FLAG_ALPHA = 0x01;
-    public static final int SAVE_FLAG_MATRIX = 0x02;
-
-    // Pops from the top of the stack as current configuration state (matrix,
-    // alpha, and clip). This call balances a previous call to save(), and is
-    // used to remove all modifications to the configuration state since the
-    // last save call.
-    public void restore();
-
-    // Draws a line using the specified paint from (x1, y1) to (x2, y2).
-    // (Both end points are included).
-    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint);
-
-    // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2).
-    // (Both end points are included).
-    public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint);
-
-    // Fills the specified rectangle with the specified color.
-    public void fillRect(float x, float y, float width, float height, int color);
-
-    // Draws a texture to the specified rectangle.
-    public void drawTexture(
-            BasicTexture texture, int x, int y, int width, int height);
-    public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
-            int uvBuffer, int indexBuffer, int indexCount);
-
-    // Draws the source rectangle part of the texture to the target rectangle.
-    public void drawTexture(BasicTexture texture, RectF source, RectF target);
-
-    // Draw a texture with a specified texture transform.
-    public void drawTexture(BasicTexture texture, float[] mTextureTransform,
-                int x, int y, int w, int h);
-
-    // Draw two textures to the specified rectangle. The actual texture used is
-    // from * (1 - ratio) + to * ratio
-    // The two textures must have the same size.
-    public void drawMixed(BasicTexture from, int toColor,
-            float ratio, int x, int y, int w, int h);
-
-    // Draw a region of a texture and a specified color to the specified
-    // rectangle. The actual color used is from * (1 - ratio) + to * ratio.
-    // The region of the texture is defined by parameter "src". The target
-    // rectangle is specified by parameter "target".
-    public void drawMixed(BasicTexture from, int toColor,
-            float ratio, RectF src, RectF target);
-
-    // Gets the underlying GL instance. This is used only when direct access to
-    // GL is needed.
-    public GL11 getGLInstance();
-
-    // Unloads the specified texture from the canvas. The resource allocated
-    // to draw the texture will be released. The specified texture will return
-    // to the unloaded state. This function should be called only from
-    // BasicTexture or its descendant
-    public boolean unloadTexture(BasicTexture texture);
-
-    // Delete the specified buffer object, similar to unloadTexture.
-    public void deleteBuffer(int bufferId);
-
-    // Delete the textures and buffers in GL side. This function should only be
-    // called in the GL thread.
-    public void deleteRecycledResources();
-
-    // Dump statistics information and clear the counters. For debug only.
-    public void dumpStatisticsAndClear();
-
-    public void beginRenderTarget(RawTexture texture);
-
-    public void endRenderTarget();
-}
diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java
deleted file mode 100644
index 45903b3..0000000
--- a/src/com/android/gallery3d/ui/GLCanvasImpl.java
+++ /dev/null
@@ -1,920 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.RectF;
-import android.opengl.GLU;
-import android.opengl.Matrix;
-
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.IntArray;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.ArrayList;
-
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11Ext;
-import javax.microedition.khronos.opengles.GL11ExtensionPack;
-
-public class GLCanvasImpl implements GLCanvas {
-    @SuppressWarnings("unused")
-    private static final String TAG = "GLCanvasImp";
-
-    private static final float OPAQUE_ALPHA = 0.95f;
-
-    private static final int OFFSET_FILL_RECT = 0;
-    private static final int OFFSET_DRAW_LINE = 4;
-    private static final int OFFSET_DRAW_RECT = 6;
-    private static final float[] BOX_COORDINATES = {
-            0, 0, 1, 0, 0, 1, 1, 1,  // used for filling a rectangle
-            0, 0, 1, 1,              // used for drawing a line
-            0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle
-
-    private final GL11 mGL;
-
-    private final float mMatrixValues[] = new float[16];
-    private final float mTextureMatrixValues[] = new float[16];
-
-    // The results of mapPoints are stored in this buffer, and the order is
-    // x1, y1, x2, y2.
-    private final float mMapPointsBuffer[] = new float[4];
-
-    private final float mTextureColor[] = new float[4];
-
-    private int mBoxCoords;
-
-    private final GLState mGLState;
-    private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>();
-
-    private float mAlpha;
-    private final ArrayList<ConfigState> mRestoreStack = new ArrayList<ConfigState>();
-    private ConfigState mRecycledRestoreAction;
-
-    private final RectF mDrawTextureSourceRect = new RectF();
-    private final RectF mDrawTextureTargetRect = new RectF();
-    private final float[] mTempMatrix = new float[32];
-    private final IntArray mUnboundTextures = new IntArray();
-    private final IntArray mDeleteBuffers = new IntArray();
-    private int mScreenWidth;
-    private int mScreenHeight;
-    private boolean mBlendEnabled = true;
-    private int mFrameBuffer[] = new int[1];
-
-    private RawTexture mTargetTexture;
-
-    // Drawing statistics
-    int mCountDrawLine;
-    int mCountFillRect;
-    int mCountDrawMesh;
-    int mCountTextureRect;
-    int mCountTextureOES;
-
-    GLCanvasImpl(GL11 gl) {
-        mGL = gl;
-        mGLState = new GLState(gl);
-        initialize();
-    }
-
-    @Override
-    public void setSize(int width, int height) {
-        Utils.assertTrue(width >= 0 && height >= 0);
-
-        if (mTargetTexture == null) {
-            mScreenWidth = width;
-            mScreenHeight = height;
-        }
-        mAlpha = 1.0f;
-
-        GL11 gl = mGL;
-        gl.glViewport(0, 0, width, height);
-        gl.glMatrixMode(GL11.GL_PROJECTION);
-        gl.glLoadIdentity();
-        GLU.gluOrtho2D(gl, 0, width, 0, height);
-
-        gl.glMatrixMode(GL11.GL_MODELVIEW);
-        gl.glLoadIdentity();
-
-        float matrix[] = mMatrixValues;
-        Matrix.setIdentityM(matrix, 0);
-        // to match the graphic coordinate system in android, we flip it vertically.
-        if (mTargetTexture == null) {
-            Matrix.translateM(matrix, 0, 0, height, 0);
-            Matrix.scaleM(matrix, 0, 1, -1, 1);
-        }
-    }
-
-    @Override
-    public void setAlpha(float alpha) {
-        Utils.assertTrue(alpha >= 0 && alpha <= 1);
-        mAlpha = alpha;
-    }
-
-    @Override
-    public float getAlpha() {
-        return mAlpha;
-    }
-
-    @Override
-    public void multiplyAlpha(float alpha) {
-        Utils.assertTrue(alpha >= 0 && alpha <= 1);
-        mAlpha *= alpha;
-    }
-
-    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
-        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
-    }
-
-    private void initialize() {
-        GL11 gl = mGL;
-
-        // First create an nio buffer, then create a VBO from it.
-        int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE;
-        FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
-        xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0);
-
-        int[] name = new int[1];
-        GLId.glGenBuffers(1, name, 0);
-        mBoxCoords = name[0];
-
-        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
-        gl.glBufferData(GL11.GL_ARRAY_BUFFER,
-                xyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
-                xyBuffer, GL11.GL_STATIC_DRAW);
-
-        gl.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
-        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
-
-        // Enable the texture coordinate array for Texture 1
-        gl.glClientActiveTexture(GL11.GL_TEXTURE1);
-        gl.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
-        gl.glClientActiveTexture(GL11.GL_TEXTURE0);
-        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
-
-        // mMatrixValues and mAlpha will be initialized in setSize()
-    }
-
-    @Override
-    public void drawRect(float x, float y, float width, float height, GLPaint paint) {
-        GL11 gl = mGL;
-
-        mGLState.setColorMode(paint.getColor(), mAlpha);
-        mGLState.setLineWidth(paint.getLineWidth());
-
-        saveTransform();
-        translate(x, y);
-        scale(width, height, 1);
-
-        gl.glLoadMatrixf(mMatrixValues, 0);
-        gl.glDrawArrays(GL11.GL_LINE_LOOP, OFFSET_DRAW_RECT, 4);
-
-        restoreTransform();
-        mCountDrawLine++;
-    }
-
-    @Override
-    public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {
-        GL11 gl = mGL;
-
-        mGLState.setColorMode(paint.getColor(), mAlpha);
-        mGLState.setLineWidth(paint.getLineWidth());
-
-        saveTransform();
-        translate(x1, y1);
-        scale(x2 - x1, y2 - y1, 1);
-
-        gl.glLoadMatrixf(mMatrixValues, 0);
-        gl.glDrawArrays(GL11.GL_LINE_STRIP, OFFSET_DRAW_LINE, 2);
-
-        restoreTransform();
-        mCountDrawLine++;
-    }
-
-    @Override
-    public void fillRect(float x, float y, float width, float height, int color) {
-        mGLState.setColorMode(color, mAlpha);
-        GL11 gl = mGL;
-
-        saveTransform();
-        translate(x, y);
-        scale(width, height, 1);
-
-        gl.glLoadMatrixf(mMatrixValues, 0);
-        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
-
-        restoreTransform();
-        mCountFillRect++;
-    }
-
-    @Override
-    public void translate(float x, float y, float z) {
-        Matrix.translateM(mMatrixValues, 0, x, y, z);
-    }
-
-    // This is a faster version of translate(x, y, z) because
-    // (1) we knows z = 0, (2) we inline the Matrix.translateM call,
-    // (3) we unroll the loop
-    @Override
-    public void translate(float x, float y) {
-        float[] m = mMatrixValues;
-        m[12] += m[0] * x + m[4] * y;
-        m[13] += m[1] * x + m[5] * y;
-        m[14] += m[2] * x + m[6] * y;
-        m[15] += m[3] * x + m[7] * y;
-    }
-
-    @Override
-    public void scale(float sx, float sy, float sz) {
-        Matrix.scaleM(mMatrixValues, 0, sx, sy, sz);
-    }
-
-    @Override
-    public void rotate(float angle, float x, float y, float z) {
-        if (angle == 0) return;
-        float[] temp = mTempMatrix;
-        Matrix.setRotateM(temp, 0, angle, x, y, z);
-        Matrix.multiplyMM(temp, 16, mMatrixValues, 0, temp, 0);
-        System.arraycopy(temp, 16, mMatrixValues, 0, 16);
-    }
-
-    @Override
-    public void multiplyMatrix(float matrix[], int offset) {
-        float[] temp = mTempMatrix;
-        Matrix.multiplyMM(temp, 0, mMatrixValues, 0, matrix, offset);
-        System.arraycopy(temp, 0, mMatrixValues, 0, 16);
-    }
-
-    private void textureRect(float x, float y, float width, float height) {
-        GL11 gl = mGL;
-
-        saveTransform();
-        translate(x, y);
-        scale(width, height, 1);
-
-        gl.glLoadMatrixf(mMatrixValues, 0);
-        gl.glDrawArrays(GL11.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, 4);
-
-        restoreTransform();
-        mCountTextureRect++;
-    }
-
-    @Override
-    public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
-            int uvBuffer, int indexBuffer, int indexCount) {
-        float alpha = mAlpha;
-        if (!bindTexture(tex)) return;
-
-        mGLState.setBlendEnabled(mBlendEnabled
-                && (!tex.isOpaque() || alpha < OPAQUE_ALPHA));
-        mGLState.setTextureAlpha(alpha);
-
-        // Reset the texture matrix. We will set our own texture coordinates
-        // below.
-        setTextureCoords(0, 0, 1, 1);
-
-        saveTransform();
-        translate(x, y);
-
-        mGL.glLoadMatrixf(mMatrixValues, 0);
-
-        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, xyBuffer);
-        mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
-
-        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, uvBuffer);
-        mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
-
-        mGL.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
-        mGL.glDrawElements(GL11.GL_TRIANGLE_STRIP,
-                indexCount, GL11.GL_UNSIGNED_BYTE, 0);
-
-        mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords);
-        mGL.glVertexPointer(2, GL11.GL_FLOAT, 0, 0);
-        mGL.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0);
-
-        restoreTransform();
-        mCountDrawMesh++;
-    }
-
-    // Transforms two points by the given matrix m. The result
-    // {x1', y1', x2', y2'} are stored in mMapPointsBuffer and also returned.
-    private float[] mapPoints(float m[], int x1, int y1, int x2, int y2) {
-        float[] r = mMapPointsBuffer;
-
-        // Multiply m and (x1 y1 0 1) to produce (x3 y3 z3 w3). z3 is unused.
-        float x3 = m[0] * x1 + m[4] * y1 + m[12];
-        float y3 = m[1] * x1 + m[5] * y1 + m[13];
-        float w3 = m[3] * x1 + m[7] * y1 + m[15];
-        r[0] = x3 / w3;
-        r[1] = y3 / w3;
-
-        // Same for x2 y2.
-        float x4 = m[0] * x2 + m[4] * y2 + m[12];
-        float y4 = m[1] * x2 + m[5] * y2 + m[13];
-        float w4 = m[3] * x2 + m[7] * y2 + m[15];
-        r[2] = x4 / w4;
-        r[3] = y4 / w4;
-
-        return r;
-    }
-
-    private void drawBoundTexture(
-            BasicTexture texture, int x, int y, int width, int height) {
-        // Test whether it has been rotated or flipped, if so, glDrawTexiOES
-        // won't work
-        if (isMatrixRotatedOrFlipped(mMatrixValues)) {
-            if (texture.hasBorder()) {
-                setTextureCoords(
-                        1.0f / texture.getTextureWidth(),
-                        1.0f / texture.getTextureHeight(),
-                        (texture.getWidth() - 1.0f) / texture.getTextureWidth(),
-                        (texture.getHeight() - 1.0f) / texture.getTextureHeight());
-            } else {
-                setTextureCoords(0, 0,
-                        (float) texture.getWidth() / texture.getTextureWidth(),
-                        (float) texture.getHeight() / texture.getTextureHeight());
-            }
-            textureRect(x, y, width, height);
-        } else {
-            // draw the rect from bottom-left to top-right
-            float points[] = mapPoints(
-                    mMatrixValues, x, y + height, x + width, y);
-            x = (int) (points[0] + 0.5f);
-            y = (int) (points[1] + 0.5f);
-            width = (int) (points[2] + 0.5f) - x;
-            height = (int) (points[3] + 0.5f) - y;
-            if (width > 0 && height > 0) {
-                ((GL11Ext) mGL).glDrawTexiOES(x, y, 0, width, height);
-                mCountTextureOES++;
-            }
-        }
-    }
-
-    @Override
-    public void drawTexture(
-            BasicTexture texture, int x, int y, int width, int height) {
-        drawTexture(texture, x, y, width, height, mAlpha);
-    }
-
-    private void drawTexture(BasicTexture texture,
-            int x, int y, int width, int height, float alpha) {
-        if (width <= 0 || height <= 0) return;
-
-        mGLState.setBlendEnabled(mBlendEnabled
-                && (!texture.isOpaque() || alpha < OPAQUE_ALPHA));
-        if (!bindTexture(texture)) return;
-        mGLState.setTextureAlpha(alpha);
-        drawBoundTexture(texture, x, y, width, height);
-    }
-
-    @Override
-    public void drawTexture(BasicTexture texture, RectF source, RectF target) {
-        if (target.width() <= 0 || target.height() <= 0) return;
-
-        // Copy the input to avoid changing it.
-        mDrawTextureSourceRect.set(source);
-        mDrawTextureTargetRect.set(target);
-        source = mDrawTextureSourceRect;
-        target = mDrawTextureTargetRect;
-
-        mGLState.setBlendEnabled(mBlendEnabled
-                && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
-        if (!bindTexture(texture)) return;
-        convertCoordinate(source, target, texture);
-        setTextureCoords(source);
-        mGLState.setTextureAlpha(mAlpha);
-        textureRect(target.left, target.top, target.width(), target.height());
-    }
-
-    @Override
-    public void drawTexture(BasicTexture texture, float[] mTextureTransform,
-            int x, int y, int w, int h) {
-        mGLState.setBlendEnabled(mBlendEnabled
-                && (!texture.isOpaque() || mAlpha < OPAQUE_ALPHA));
-        if (!bindTexture(texture)) return;
-        setTextureCoords(mTextureTransform);
-        mGLState.setTextureAlpha(mAlpha);
-        textureRect(x, y, w, h);
-    }
-
-    // This function changes the source coordinate to the texture coordinates.
-    // It also clips the source and target coordinates if it is beyond the
-    // bound of the texture.
-    private static void convertCoordinate(RectF source, RectF target,
-            BasicTexture texture) {
-
-        int width = texture.getWidth();
-        int height = texture.getHeight();
-        int texWidth = texture.getTextureWidth();
-        int texHeight = texture.getTextureHeight();
-        // Convert to texture coordinates
-        source.left /= texWidth;
-        source.right /= texWidth;
-        source.top /= texHeight;
-        source.bottom /= texHeight;
-
-        // Clip if the rendering range is beyond the bound of the texture.
-        float xBound = (float) width / texWidth;
-        if (source.right > xBound) {
-            target.right = target.left + target.width() *
-                    (xBound - source.left) / source.width();
-            source.right = xBound;
-        }
-        float yBound = (float) height / texHeight;
-        if (source.bottom > yBound) {
-            target.bottom = target.top + target.height() *
-                    (yBound - source.top) / source.height();
-            source.bottom = yBound;
-        }
-    }
-
-    @Override
-    public void drawMixed(BasicTexture from,
-            int toColor, float ratio, int x, int y, int w, int h) {
-        drawMixed(from, toColor, ratio, x, y, w, h, mAlpha);
-    }
-
-    private boolean bindTexture(BasicTexture texture) {
-        if (!texture.onBind(this)) return false;
-        int target = texture.getTarget();
-        mGLState.setTextureTarget(target);
-        mGL.glBindTexture(target, texture.getId());
-        return true;
-    }
-
-    private void setTextureColor(float r, float g, float b, float alpha) {
-        float[] color = mTextureColor;
-        color[0] = r;
-        color[1] = g;
-        color[2] = b;
-        color[3] = alpha;
-    }
-
-    private void setMixedColor(int toColor, float ratio, float alpha) {
-        //
-        // The formula we want:
-        //     alpha * ((1 - ratio) * from + ratio * to)
-        //
-        // The formula that GL supports is in the form of:
-        //     combo * from + (1 - combo) * to * scale
-        //
-        // So, we have combo = alpha * (1 - ratio)
-        //     and     scale = alpha * ratio / (1 - combo)
-        //
-        float combo = alpha * (1 - ratio);
-        float scale = alpha * ratio / (1 - combo);
-
-        // Specify the interpolation factor via the alpha component of
-        // GL_TEXTURE_ENV_COLORs.
-        // RGB component are get from toColor and will used as SRC1
-        float colorScale = scale * (toColor >>> 24) / (0xff * 0xff);
-        setTextureColor(((toColor >>> 16) & 0xff) * colorScale,
-                ((toColor >>> 8) & 0xff) * colorScale,
-                (toColor & 0xff) * colorScale, combo);
-        GL11 gl = mGL;
-        gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0);
-
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_ALPHA, GL11.GL_INTERPOLATE);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_RGB, GL11.GL_CONSTANT);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC1_ALPHA, GL11.GL_CONSTANT);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA);
-
-        // Wire up the interpolation factor for RGB.
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_RGB, GL11.GL_CONSTANT);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA);
-
-        // Wire up the interpolation factor for alpha.
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT);
-        gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA);
-
-    }
-
-    @Override
-    public void drawMixed(BasicTexture from, int toColor, float ratio,
-            RectF source, RectF target) {
-        if (target.width() <= 0 || target.height() <= 0) return;
-
-        if (ratio <= 0.01f) {
-            drawTexture(from, source, target);
-            return;
-        } else if (ratio >= 1) {
-            fillRect(target.left, target.top, target.width(), target.height(), toColor);
-            return;
-        }
-
-        float alpha = mAlpha;
-
-        // Copy the input to avoid changing it.
-        mDrawTextureSourceRect.set(source);
-        mDrawTextureTargetRect.set(target);
-        source = mDrawTextureSourceRect;
-        target = mDrawTextureTargetRect;
-
-        mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
-                || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
-
-        if (!bindTexture(from)) return;
-
-        // Interpolate the RGB and alpha values between both textures.
-        mGLState.setTexEnvMode(GL11.GL_COMBINE);
-        setMixedColor(toColor, ratio, alpha);
-        convertCoordinate(source, target, from);
-        setTextureCoords(source);
-        textureRect(target.left, target.top, target.width(), target.height());
-        mGLState.setTexEnvMode(GL11.GL_REPLACE);
-    }
-
-    private void drawMixed(BasicTexture from, int toColor,
-            float ratio, int x, int y, int width, int height, float alpha) {
-        // change from 0 to 0.01f to prevent getting divided by zero below
-        if (ratio <= 0.01f) {
-            drawTexture(from, x, y, width, height, alpha);
-            return;
-        } else if (ratio >= 1) {
-            fillRect(x, y, width, height, toColor);
-            return;
-        }
-
-        mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque()
-                || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA));
-
-        final GL11 gl = mGL;
-        if (!bindTexture(from)) return;
-
-        // Interpolate the RGB and alpha values between both textures.
-        mGLState.setTexEnvMode(GL11.GL_COMBINE);
-        setMixedColor(toColor, ratio, alpha);
-
-        drawBoundTexture(from, x, y, width, height);
-        mGLState.setTexEnvMode(GL11.GL_REPLACE);
-    }
-
-    // TODO: the code only work for 2D should get fixed for 3D or removed
-    private static final int MSKEW_X = 4;
-    private static final int MSKEW_Y = 1;
-    private static final int MSCALE_X = 0;
-    private static final int MSCALE_Y = 5;
-
-    private static boolean isMatrixRotatedOrFlipped(float matrix[]) {
-        final float eps = 1e-5f;
-        return Math.abs(matrix[MSKEW_X]) > eps
-                || Math.abs(matrix[MSKEW_Y]) > eps
-                || matrix[MSCALE_X] < -eps
-                || matrix[MSCALE_Y] > eps;
-    }
-
-    private static class GLState {
-
-        private final GL11 mGL;
-
-        private int mTexEnvMode = GL11.GL_REPLACE;
-        private float mTextureAlpha = 1.0f;
-        private int mTextureTarget = GL11.GL_TEXTURE_2D;
-        private boolean mBlendEnabled = true;
-        private float mLineWidth = 1.0f;
-        private boolean mLineSmooth = false;
-
-        public GLState(GL11 gl) {
-            mGL = gl;
-
-            // Disable unused state
-            gl.glDisable(GL11.GL_LIGHTING);
-
-            // Enable used features
-            gl.glEnable(GL11.GL_DITHER);
-
-            gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
-            gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
-            gl.glEnable(GL11.GL_TEXTURE_2D);
-
-            gl.glTexEnvf(GL11.GL_TEXTURE_ENV,
-                    GL11.GL_TEXTURE_ENV_MODE, GL11.GL_REPLACE);
-
-            // Set the background color
-            gl.glClearColor(0f, 0f, 0f, 0f);
-            gl.glClearStencil(0);
-
-            gl.glEnable(GL11.GL_BLEND);
-            gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
-
-            // We use 565 or 8888 format, so set the alignment to 2 bytes/pixel.
-            gl.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
-        }
-
-        public void setTexEnvMode(int mode) {
-            if (mTexEnvMode == mode) return;
-            mTexEnvMode = mode;
-            mGL.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode);
-        }
-
-        public void setLineWidth(float width) {
-            if (mLineWidth == width) return;
-            mLineWidth = width;
-            mGL.glLineWidth(width);
-        }
-
-        public void setTextureAlpha(float alpha) {
-            if (mTextureAlpha == alpha) return;
-            mTextureAlpha = alpha;
-            if (alpha >= OPAQUE_ALPHA) {
-                // The alpha is need for those texture without alpha channel
-                mGL.glColor4f(1, 1, 1, 1);
-                setTexEnvMode(GL11.GL_REPLACE);
-            } else {
-                mGL.glColor4f(alpha, alpha, alpha, alpha);
-                setTexEnvMode(GL11.GL_MODULATE);
-            }
-        }
-
-        public void setColorMode(int color, float alpha) {
-            setBlendEnabled(!Utils.isOpaque(color) || alpha < OPAQUE_ALPHA);
-
-            // Set mTextureAlpha to an invalid value, so that it will reset
-            // again in setTextureAlpha(float) later.
-            mTextureAlpha = -1.0f;
-
-            setTextureTarget(0);
-
-            float prealpha = (color >>> 24) * alpha * 65535f / 255f / 255f;
-            mGL.glColor4x(
-                    Math.round(((color >> 16) & 0xFF) * prealpha),
-                    Math.round(((color >> 8) & 0xFF) * prealpha),
-                    Math.round((color & 0xFF) * prealpha),
-                    Math.round(255 * prealpha));
-        }
-
-        // target is a value like GL_TEXTURE_2D. If target = 0, texturing is disabled.
-        public void setTextureTarget(int target) {
-            if (mTextureTarget == target) return;
-            if (mTextureTarget != 0) {
-                mGL.glDisable(mTextureTarget);
-            }
-            mTextureTarget = target;
-            if (mTextureTarget != 0) {
-                mGL.glEnable(mTextureTarget);
-            }
-        }
-
-        public void setBlendEnabled(boolean enabled) {
-            if (mBlendEnabled == enabled) return;
-            mBlendEnabled = enabled;
-            if (enabled) {
-                mGL.glEnable(GL11.GL_BLEND);
-            } else {
-                mGL.glDisable(GL11.GL_BLEND);
-            }
-        }
-    }
-
-    @Override
-    public GL11 getGLInstance() {
-        return mGL;
-    }
-
-    @Override
-    public void clearBuffer(float[] argb) {
-        if(argb != null && argb.length == 4) {
-            mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]);
-        } else {
-            mGL.glClearColor(0, 0, 0, 1);
-        }
-        mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
-    }
-
-    @Override
-    public void clearBuffer() {
-        clearBuffer(null);
-    }
-
-    private void setTextureCoords(RectF source) {
-        setTextureCoords(source.left, source.top, source.right, source.bottom);
-    }
-
-    private void setTextureCoords(float left, float top,
-            float right, float bottom) {
-        mGL.glMatrixMode(GL11.GL_TEXTURE);
-        mTextureMatrixValues[0] = right - left;
-        mTextureMatrixValues[5] = bottom - top;
-        mTextureMatrixValues[10] = 1;
-        mTextureMatrixValues[12] = left;
-        mTextureMatrixValues[13] = top;
-        mTextureMatrixValues[15] = 1;
-        mGL.glLoadMatrixf(mTextureMatrixValues, 0);
-        mGL.glMatrixMode(GL11.GL_MODELVIEW);
-    }
-
-    private void setTextureCoords(float[] mTextureTransform) {
-        mGL.glMatrixMode(GL11.GL_TEXTURE);
-        mGL.glLoadMatrixf(mTextureTransform, 0);
-        mGL.glMatrixMode(GL11.GL_MODELVIEW);
-    }
-
-    // unloadTexture and deleteBuffer can be called from the finalizer thread,
-    // so we synchronized on the mUnboundTextures object.
-    @Override
-    public boolean unloadTexture(BasicTexture t) {
-        synchronized (mUnboundTextures) {
-            if (!t.isLoaded()) return false;
-            mUnboundTextures.add(t.mId);
-            return true;
-        }
-    }
-
-    @Override
-    public void deleteBuffer(int bufferId) {
-        synchronized (mUnboundTextures) {
-            mDeleteBuffers.add(bufferId);
-        }
-    }
-
-    @Override
-    public void deleteRecycledResources() {
-        synchronized (mUnboundTextures) {
-            IntArray ids = mUnboundTextures;
-            if (ids.size() > 0) {
-                GLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0);
-                ids.clear();
-            }
-
-            ids = mDeleteBuffers;
-            if (ids.size() > 0) {
-                GLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0);
-                ids.clear();
-            }
-        }
-    }
-
-    @Override
-    public void save() {
-        save(SAVE_FLAG_ALL);
-    }
-
-    @Override
-    public void save(int saveFlags) {
-        ConfigState config = obtainRestoreConfig();
-
-        if ((saveFlags & SAVE_FLAG_ALPHA) != 0) {
-            config.mAlpha = mAlpha;
-        } else {
-            config.mAlpha = -1;
-        }
-
-
-        if ((saveFlags & SAVE_FLAG_MATRIX) != 0) {
-            System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16);
-        } else {
-            config.mMatrix[0] = Float.NEGATIVE_INFINITY;
-        }
-
-        mRestoreStack.add(config);
-    }
-
-    @Override
-    public void restore() {
-        if (mRestoreStack.isEmpty()) throw new IllegalStateException();
-        ConfigState config = mRestoreStack.remove(mRestoreStack.size() - 1);
-        config.restore(this);
-        freeRestoreConfig(config);
-    }
-
-    private void freeRestoreConfig(ConfigState action) {
-        action.mNextFree = mRecycledRestoreAction;
-        mRecycledRestoreAction = action;
-    }
-
-    private ConfigState obtainRestoreConfig() {
-        if (mRecycledRestoreAction != null) {
-            ConfigState result = mRecycledRestoreAction;
-            mRecycledRestoreAction = result.mNextFree;
-            return result;
-        }
-        return new ConfigState();
-    }
-
-    private static class ConfigState {
-        float mAlpha;
-        float mMatrix[] = new float[16];
-        ConfigState mNextFree;
-
-        public void restore(GLCanvasImpl canvas) {
-            if (mAlpha >= 0) canvas.setAlpha(mAlpha);
-            if (mMatrix[0] != Float.NEGATIVE_INFINITY) {
-                System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16);
-            }
-        }
-    }
-
-    @Override
-    public void dumpStatisticsAndClear() {
-        String line = String.format(
-                "MESH:%d, TEX_OES:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d",
-                mCountDrawMesh, mCountTextureRect, mCountTextureOES,
-                mCountFillRect, mCountDrawLine);
-        mCountDrawMesh = 0;
-        mCountTextureRect = 0;
-        mCountTextureOES = 0;
-        mCountFillRect = 0;
-        mCountDrawLine = 0;
-        Log.d(TAG, line);
-    }
-
-    private void saveTransform() {
-        System.arraycopy(mMatrixValues, 0, mTempMatrix, 0, 16);
-    }
-
-    private void restoreTransform() {
-        System.arraycopy(mTempMatrix, 0, mMatrixValues, 0, 16);
-    }
-
-    private void setRenderTarget(RawTexture texture) {
-        GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL;
-
-        if (mTargetTexture == null && texture != null) {
-            GLId.glGenBuffers(1, mFrameBuffer, 0);
-            gl11ep.glBindFramebufferOES(
-                    GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]);
-        }
-        if (mTargetTexture != null && texture  == null) {
-            gl11ep.glBindFramebufferOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES, 0);
-            gl11ep.glDeleteFramebuffersOES(1, mFrameBuffer, 0);
-        }
-
-        mTargetTexture = texture;
-        if (texture == null) {
-            setSize(mScreenWidth, mScreenHeight);
-        } else {
-            setSize(texture.getWidth(), texture.getHeight());
-
-            if (!texture.isLoaded()) texture.prepare(this);
-
-            gl11ep.glFramebufferTexture2DOES(
-                    GL11ExtensionPack.GL_FRAMEBUFFER_OES,
-                    GL11ExtensionPack.GL_COLOR_ATTACHMENT0_OES,
-                    GL11.GL_TEXTURE_2D, texture.getId(), 0);
-
-            checkFramebufferStatus(gl11ep);
-        }
-    }
-
-    @Override
-    public void endRenderTarget() {
-        RawTexture texture = mTargetStack.remove(mTargetStack.size() - 1);
-        setRenderTarget(texture);
-        restore(); // restore matrix and alpha
-    }
-
-    @Override
-    public void beginRenderTarget(RawTexture texture) {
-        save(); // save matrix and alpha
-        mTargetStack.add(mTargetTexture);
-        setRenderTarget(texture);
-    }
-
-    private static void checkFramebufferStatus(GL11ExtensionPack gl11ep) {
-        int status = gl11ep.glCheckFramebufferStatusOES(GL11ExtensionPack.GL_FRAMEBUFFER_OES);
-        if (status != GL11ExtensionPack.GL_FRAMEBUFFER_COMPLETE_OES) {
-            String msg = "";
-            switch (status) {
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
-                    msg = "FRAMEBUFFER_FORMATS";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
-                    msg = "FRAMEBUFFER_ATTACHMENT";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
-                    msg = "FRAMEBUFFER_MISSING_ATTACHMENT";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_OES:
-                    msg = "FRAMEBUFFER_DRAW_BUFFER";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_OES:
-                    msg = "FRAMEBUFFER_READ_BUFFER";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_UNSUPPORTED_OES:
-                    msg = "FRAMEBUFFER_UNSUPPORTED";
-                    break;
-                case GL11ExtensionPack.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
-                    msg = "FRAMEBUFFER_INCOMPLETE_DIMENSIONS";
-                    break;
-            }
-            throw new RuntimeException(msg + ":" + Integer.toHexString(status));
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/GLId.java b/src/com/android/gallery3d/ui/GLId.java
deleted file mode 100644
index 689cf19..0000000
--- a/src/com/android/gallery3d/ui/GLId.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11ExtensionPack;
-
-// This mimics corresponding GL functions.
-public class GLId {
-    static int sNextId = 1;
-
-    public synchronized static void glGenTextures(int n, int[] textures, int offset) {
-        while (n-- > 0) {
-            textures[offset + n] = sNextId++;
-        }
-    }
-
-    public synchronized static void glGenBuffers(int n, int[] buffers, int offset) {
-        while (n-- > 0) {
-            buffers[offset + n] = sNextId++;
-        }
-    }
-
-    public synchronized static void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) {
-        gl.glDeleteTextures(n, textures, offset);
-    }
-
-    public synchronized static void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) {
-        gl.glDeleteBuffers(n, buffers, offset);
-    }
-
-    public synchronized static void glDeleteFramebuffers(
-            GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) {
-        gl11ep.glDeleteFramebuffersOES(n, buffers, offset);
-    }
-}
diff --git a/src/com/android/gallery3d/ui/GLPaint.java b/src/com/android/gallery3d/ui/GLPaint.java
deleted file mode 100644
index eb75cc5..0000000
--- a/src/com/android/gallery3d/ui/GLPaint.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import com.android.gallery3d.common.Utils;
-
-
-public class GLPaint {
-    private float mLineWidth = 1f;
-    private int mColor = 0;
-
-    public void setColor(int color) {
-        mColor = color;
-    }
-
-    public int getColor() {
-        return mColor;
-    }
-
-    public void setLineWidth(float width) {
-        Utils.assertTrue(width >= 0);
-        mLineWidth = width;
-    }
-
-    public float getLineWidth() {
-        return mLineWidth;
-    }
-}
diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java
index e406b67..33a82ea 100644
--- a/src/com/android/gallery3d/ui/GLRoot.java
+++ b/src/com/android/gallery3d/ui/GLRoot.java
@@ -20,6 +20,7 @@
 import android.graphics.Matrix;
 
 import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 public interface GLRoot {
 
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index b7c48bf..f00bd54 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -33,6 +33,11 @@
 import com.android.gallery3d.anim.CanvasAnimation;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLES11Canvas;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.MotionEventHelper;
 import com.android.gallery3d.util.Profile;
@@ -89,9 +94,6 @@
     private int mFlags = FLAG_NEED_LAYOUT;
     private volatile boolean mRenderRequested = false;
 
-    private final GalleryEGLConfigChooser mEglConfigChooser =
-            new GalleryEGLConfigChooser();
-
     private final ArrayList<CanvasAnimation> mAnimations =
             new ArrayList<CanvasAnimation>();
 
@@ -117,7 +119,12 @@
         super(context, attrs);
         mFlags |= FLAG_INITIALIZED;
         setBackgroundDrawable(null);
-        setEGLConfigChooser(mEglConfigChooser);
+        setEGLContextClientVersion(ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1);
+        if (ApiHelper.USE_888_PIXEL_FORMAT) {
+            setEGLConfigChooser(8, 8, 8, 0, 0, 0);
+        } else {
+            setEGLConfigChooser(5, 6, 5, 0, 0, 0);
+        }
         setRenderer(this);
         if (ApiHelper.USE_888_PIXEL_FORMAT) {
             getHolder().setFormat(PixelFormat.RGB_888);
@@ -283,7 +290,7 @@
         mRenderLock.lock();
         try {
             mGL = gl;
-            mCanvas = new GLCanvasImpl(gl);
+            mCanvas = ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLES11Canvas(gl);
             BasicTexture.invalidateAllTextures();
         } finally {
             mRenderLock.unlock();
diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java
index 664012c..83de19f 100644
--- a/src/com/android/gallery3d/ui/GLView.java
+++ b/src/com/android/gallery3d/ui/GLView.java
@@ -23,6 +23,7 @@
 import com.android.gallery3d.anim.CanvasAnimation;
 import com.android.gallery3d.anim.StateTransitionAnimation;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 import java.util.ArrayList;
 
diff --git a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
deleted file mode 100644
index deeb3b7..0000000
--- a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.gallery3d.ui;
-
-import android.opengl.GLSurfaceView.EGLConfigChooser;
-
-import com.android.gallery3d.common.ApiHelper;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLDisplay;
-
-/*
- * The code is copied/adapted from
- * <code>android.opengl.GLSurfaceView.BaseConfigChooser</code>. Here we try to
- * choose a configuration that support RGBA_8888 format and if possible,
- * with stencil buffer, but is not required.
- */
-class GalleryEGLConfigChooser implements EGLConfigChooser {
-
-    private static final String TAG = "GalleryEGLConfigChooser";
-
-    private final int mConfigSpec565[] = new int[] {
-            EGL10.EGL_RED_SIZE, 5,
-            EGL10.EGL_GREEN_SIZE, 6,
-            EGL10.EGL_BLUE_SIZE, 5,
-            EGL10.EGL_ALPHA_SIZE, 0,
-            EGL10.EGL_NONE
-    };
-
-    private final int mConfigSpec888[] = new int[] {
-            EGL10.EGL_RED_SIZE, 8,
-            EGL10.EGL_GREEN_SIZE, 8,
-            EGL10.EGL_BLUE_SIZE, 8,
-            EGL10.EGL_ALPHA_SIZE, 0,
-            EGL10.EGL_NONE
-    };
-
-    @Override
-    public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
-        int[] numConfig = new int[1];
-        int mConfigSpec[] = ApiHelper.USE_888_PIXEL_FORMAT
-                ? mConfigSpec888 : mConfigSpec565;
-        if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) {
-            throw new RuntimeException("eglChooseConfig failed");
-        }
-
-        if (numConfig[0] <= 0) {
-            throw new RuntimeException("No configs match configSpec");
-        }
-
-        EGLConfig[] configs = new EGLConfig[numConfig[0]];
-        if (!egl.eglChooseConfig(display,
-                mConfigSpec, configs, configs.length, numConfig)) {
-            throw new RuntimeException();
-        }
-
-        return chooseConfig(egl, display, configs);
-    }
-
-    private EGLConfig chooseConfig(
-            EGL10 egl, EGLDisplay display, EGLConfig configs[]) {
-
-        EGLConfig result = null;
-        int minStencil = Integer.MAX_VALUE;
-        int value[] = new int[1];
-
-        // Because we need only one bit of stencil, try to choose a config that
-        // has stencil support but with smallest number of stencil bits. If
-        // none is found, choose any one.
-        for (int i = 0, n = configs.length; i < n; ++i) {
-            if (!ApiHelper.USE_888_PIXEL_FORMAT) {
-                if (egl.eglGetConfigAttrib(
-                    display, configs[i], EGL10.EGL_RED_SIZE, value)) {
-                    // Filter out ARGB 8888 configs.
-                    if (value[0] == 8) continue;
-                }
-            }
-            if (egl.eglGetConfigAttrib(
-                    display, configs[i], EGL10.EGL_STENCIL_SIZE, value)) {
-                if (value[0] == 0) continue;
-                if (value[0] < minStencil) {
-                    minStencil = value[0];
-                    result = configs[i];
-                }
-            } else {
-                throw new RuntimeException(
-                        "eglGetConfigAttrib error: " + egl.eglGetError());
-            }
-        }
-        if (result == null) result = configs[0];
-        egl.eglGetConfigAttrib(
-                display, result, EGL10.EGL_STENCIL_SIZE, value);
-        logConfig(egl, display, result);
-        return result;
-    }
-
-    private static final int[] ATTR_ID = {
-            EGL10.EGL_RED_SIZE,
-            EGL10.EGL_GREEN_SIZE,
-            EGL10.EGL_BLUE_SIZE,
-            EGL10.EGL_ALPHA_SIZE,
-            EGL10.EGL_DEPTH_SIZE,
-            EGL10.EGL_STENCIL_SIZE,
-            EGL10.EGL_CONFIG_ID,
-            EGL10.EGL_CONFIG_CAVEAT
-    };
-
-    private static final String[] ATTR_NAME = {
-        "R", "G", "B", "A", "D", "S", "ID", "CAVEAT"
-    };
-
-    private void logConfig(EGL10 egl, EGLDisplay display, EGLConfig config) {
-        int value[] = new int[1];
-        StringBuilder sb = new StringBuilder();
-        for (int j = 0; j < ATTR_ID.length; j++) {
-            egl.eglGetConfigAttrib(display, config, ATTR_ID[j], value);
-            sb.append(ATTR_NAME[j] + value[0] + " ");
-        }
-        Log.i(TAG, "Config chosen: " + sb.toString());
-    }
-}
diff --git a/src/com/android/gallery3d/ui/GestureRecognizer.java b/src/com/android/gallery3d/ui/GestureRecognizer.java
index e4e0c49..1e5250b 100644
--- a/src/com/android/gallery3d/ui/GestureRecognizer.java
+++ b/src/com/android/gallery3d/ui/GestureRecognizer.java
@@ -32,7 +32,7 @@
         boolean onSingleTapUp(float x, float y);
         boolean onDoubleTap(float x, float y);
         boolean onScroll(float dx, float dy, float totalX, float totalY);
-        boolean onFling(float velocityX, float velocityY);
+        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
         boolean onScaleBegin(float focusX, float focusY);
         boolean onScale(float focusX, float focusY, float scale);
         void onScaleEnd();
@@ -94,7 +94,7 @@
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                 float velocityY) {
-            return mListener.onFling(velocityX, velocityY);
+            return mListener.onFling(e1, e2, velocityX, velocityY);
         }
     }
 
diff --git a/src/com/android/gallery3d/ui/ImportCompleteListener.java b/src/com/android/gallery3d/ui/ImportCompleteListener.java
deleted file mode 100644
index 8d6e981..0000000
--- a/src/com/android/gallery3d/ui/ImportCompleteListener.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.ui;
-
-import android.os.Bundle;
-import android.widget.Toast;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.app.AbstractGalleryActivity;
-import com.android.gallery3d.app.AlbumPage;
-import com.android.gallery3d.util.MediaSetUtils;
-
-public class ImportCompleteListener extends WakeLockHoldingProgressListener {
-    private static final String WAKE_LOCK_LABEL = "Gallery Album Import";
-
-    public ImportCompleteListener(AbstractGalleryActivity galleryActivity) {
-        super(galleryActivity, WAKE_LOCK_LABEL);
-    }
-
-    @Override
-    public void onProgressComplete(int result) {
-        super.onProgressComplete(result);
-        int message;
-        if (result == MenuExecutor.EXECUTION_RESULT_SUCCESS) {
-            message = R.string.import_complete;
-            goToImportedAlbum();
-        } else {
-            message = R.string.import_fail;
-        }
-        Toast.makeText(getActivity().getAndroidContext(), message, Toast.LENGTH_LONG).show();
-    }
-
-    private void goToImportedAlbum() {
-        String pathOfImportedAlbum = "/local/all/" + MediaSetUtils.IMPORTED_BUCKET_ID;
-        Bundle data = new Bundle();
-        data.putString(AlbumPage.KEY_MEDIA_PATH, pathOfImportedAlbum);
-        getActivity().getStateManager().startState(AlbumPage.class, data);
-    }
-}
diff --git a/src/com/android/gallery3d/ui/Log.java b/src/com/android/gallery3d/ui/Log.java
index 32adc98..5570763 100644
--- a/src/com/android/gallery3d/ui/Log.java
+++ b/src/com/android/gallery3d/ui/Log.java
@@ -16,6 +16,7 @@
 
 package com.android.gallery3d.ui;
 
+// TODO: Delete this
 public class Log {
     public static int v(String tag, String msg) {
         return android.util.Log.v(tag, msg);
diff --git a/src/com/android/gallery3d/ui/ManageCacheDrawer.java b/src/com/android/gallery3d/ui/ManageCacheDrawer.java
index e989af2..d210bd1 100644
--- a/src/com/android/gallery3d/ui/ManageCacheDrawer.java
+++ b/src/com/android/gallery3d/ui/ManageCacheDrawer.java
@@ -23,6 +23,9 @@
 import com.android.gallery3d.data.DataSourceType;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.ResourceTexture;
+import com.android.gallery3d.glrenderer.StringTexture;
 import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry;
 
 public class ManageCacheDrawer extends AlbumSetSlotRenderer {
diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java
index 4285f44..8f4854e 100644
--- a/src/com/android/gallery3d/ui/MenuExecutor.java
+++ b/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -31,13 +31,12 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.app.AbstractGalleryActivity;
-import com.android.gallery3d.app.CropImage;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.Path;
-import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.filtershow.crop.CropActivity;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.ThreadPool.Job;
@@ -62,6 +61,7 @@
     private Future<?> mTask;
     // wait the operation to finish when we want to stop it.
     private boolean mWaitOnStop;
+    private boolean mPaused;
 
     private final AbstractGalleryActivity mActivity;
     private final SelectionManager mSelectionManager;
@@ -113,7 +113,7 @@
                         break;
                     }
                     case MSG_TASK_UPDATE: {
-                        if (mDialog != null) mDialog.setProgress(message.arg1);
+                        if (mDialog != null && !mPaused) mDialog.setProgress(message.arg1);
                         if (message.obj != null) {
                             ProgressListener listener = (ProgressListener) message.obj;
                             listener.onProgressUpdate(message.arg1);
@@ -132,14 +132,23 @@
     private void stopTaskAndDismissDialog() {
         if (mTask != null) {
             if (!mWaitOnStop) mTask.cancel();
-            mTask.waitDone();
-            mDialog.dismiss();
+            if (mDialog != null && mDialog.isShowing()) mDialog.dismiss();
             mDialog = null;
             mTask = null;
         }
     }
 
+    public void resume() {
+        mPaused = false;
+        if (mDialog != null) mDialog.show();
+    }
+
     public void pause() {
+        mPaused = true;
+        if (mDialog != null && mDialog.isShowing()) mDialog.hide();
+    }
+
+    public void destroy() {
         stopTaskAndDismissDialog();
     }
 
@@ -161,27 +170,28 @@
         boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0;
         boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0;
         boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0;
+        boolean supportMute = (supported & MediaObject.SUPPORT_MUTE) != 0;
         boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0;
         boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0;
         boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0;
         boolean supportCache = (supported & MediaObject.SUPPORT_CACHE) != 0;
         boolean supportEdit = (supported & MediaObject.SUPPORT_EDIT) != 0;
         boolean supportInfo = (supported & MediaObject.SUPPORT_INFO) != 0;
-        boolean supportImport = (supported & MediaObject.SUPPORT_IMPORT) != 0;
 
         setMenuItemVisible(menu, R.id.action_delete, supportDelete);
         setMenuItemVisible(menu, R.id.action_rotate_ccw, supportRotate);
         setMenuItemVisible(menu, R.id.action_rotate_cw, supportRotate);
         setMenuItemVisible(menu, R.id.action_crop, supportCrop);
         setMenuItemVisible(menu, R.id.action_trim, supportTrim);
+        setMenuItemVisible(menu, R.id.action_mute, supportMute);
         // Hide panorama until call to updateMenuForPanorama corrects it
         setMenuItemVisible(menu, R.id.action_share_panorama, false);
         setMenuItemVisible(menu, R.id.action_share, supportShare);
         setMenuItemVisible(menu, R.id.action_setas, supportSetAs);
         setMenuItemVisible(menu, R.id.action_show_on_map, supportShowOnMap);
         setMenuItemVisible(menu, R.id.action_edit, supportEdit);
+        // setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit);
         setMenuItemVisible(menu, R.id.action_details, supportInfo);
-        setMenuItemVisible(menu, R.id.action_import, supportImport);
     }
 
     public static void updateMenuForPanorama(Menu menu, boolean shareAsPanorama360,
@@ -227,8 +237,7 @@
                 }
                 return;
             case R.id.action_crop: {
-                Intent intent = getIntentBySingleSelectedPath(FilterShowActivity.CROP_ACTION)
-                        .setClass((Activity) mActivity, FilterShowActivity.class);
+                Intent intent = getIntentBySingleSelectedPath(CropActivity.CROP_ACTION);
                 ((Activity) mActivity).startActivity(intent);
                 return;
             }
@@ -259,9 +268,6 @@
             case R.id.action_show_on_map:
                 title = R.string.show_on_map;
                 break;
-            case R.id.action_import:
-                title = R.string.Import;
-                break;
             default:
                 return;
         }
@@ -327,15 +333,26 @@
         stopTaskAndDismissDialog();
 
         Activity activity = mActivity;
-        mDialog = createProgressDialog(activity, title, ids.size());
         if (showDialog) {
+            mDialog = createProgressDialog(activity, title, ids.size());
             mDialog.show();
+        } else {
+            mDialog = null;
         }
         MediaOperation operation = new MediaOperation(action, ids, listener);
-        mTask = mActivity.getThreadPool().submit(operation, null);
+        mTask = mActivity.getBatchServiceThreadPoolIfAvailable().submit(operation, null);
         mWaitOnStop = waitOnStop;
     }
 
+    public void startSingleItemAction(int action, Path targetPath) {
+        ArrayList<Path> ids = new ArrayList<Path>(1);
+        ids.add(targetPath);
+        mDialog = null;
+        MediaOperation operation = new MediaOperation(action, ids, null);
+        mTask = mActivity.getBatchServiceThreadPoolIfAvailable().submit(operation, null);
+        mWaitOnStop = false;
+    }
+
     public static String getMimeType(int type) {
         switch (type) {
             case MediaObject.MEDIA_TYPE_IMAGE :
@@ -382,11 +399,6 @@
                 }
                 break;
             }
-            case R.id.action_import: {
-                MediaObject obj = manager.getMediaObject(path);
-                result = obj.Import();
-                break;
-            }
             default:
                 throw new AssertionError();
         }
@@ -422,7 +434,7 @@
                     if (!execute(manager, jc, mOperation, id)) {
                         result = EXECUTION_RESULT_FAIL;
                     }
-                    onProgressUpdate(++index, mListener);
+                    onProgressUpdate(index++, mListener);
                 }
             } catch (Throwable th) {
                 Log.e(TAG, "failed to execute operation " + mOperation
diff --git a/src/com/android/gallery3d/ui/MultiLineTexture.java b/src/com/android/gallery3d/ui/MultiLineTexture.java
deleted file mode 100644
index b0c3c2b..0000000
--- a/src/com/android/gallery3d/ui/MultiLineTexture.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.text.Layout;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-
-// MultiLineTexture is a texture shows the content of a specified String.
-//
-// To create a MultiLineTexture, use the newInstance() method and specify
-// the String, the font size, and the color.
-class MultiLineTexture extends CanvasTexture {
-    private final Layout mLayout;
-
-    private MultiLineTexture(Layout layout) {
-        super(layout.getWidth(), layout.getHeight());
-        mLayout = layout;
-    }
-
-    public static MultiLineTexture newInstance(
-            String text, int maxWidth, float textSize, int color,
-            Layout.Alignment alignment) {
-        TextPaint paint = StringTexture.getDefaultPaint(textSize, color);
-        Layout layout = new StaticLayout(text, 0, text.length(), paint,
-                maxWidth, alignment, 1, 0, true, null, 0);
-
-        return new MultiLineTexture(layout);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas, Bitmap backing) {
-        mLayout.draw(canvas);
-    }
-}
diff --git a/src/com/android/gallery3d/ui/NinePatchChunk.java b/src/com/android/gallery3d/ui/NinePatchChunk.java
deleted file mode 100644
index 61bf22c..0000000
--- a/src/com/android/gallery3d/ui/NinePatchChunk.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Rect;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-// See "frameworks/base/include/utils/ResourceTypes.h" for the format of
-// NinePatch chunk.
-class NinePatchChunk {
-
-    public static final int NO_COLOR = 0x00000001;
-    public static final int TRANSPARENT_COLOR = 0x00000000;
-
-    public Rect mPaddings = new Rect();
-
-    public int mDivX[];
-    public int mDivY[];
-    public int mColor[];
-
-    private static void readIntArray(int[] data, ByteBuffer buffer) {
-        for (int i = 0, n = data.length; i < n; ++i) {
-            data[i] = buffer.getInt();
-        }
-    }
-
-    private static void checkDivCount(int length) {
-        if (length == 0 || (length & 0x01) != 0) {
-            throw new RuntimeException("invalid nine-patch: " + length);
-        }
-    }
-
-    public static NinePatchChunk deserialize(byte[] data) {
-        ByteBuffer byteBuffer =
-                ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
-
-        byte wasSerialized = byteBuffer.get();
-        if (wasSerialized == 0) return null;
-
-        NinePatchChunk chunk = new NinePatchChunk();
-        chunk.mDivX = new int[byteBuffer.get()];
-        chunk.mDivY = new int[byteBuffer.get()];
-        chunk.mColor = new int[byteBuffer.get()];
-
-        checkDivCount(chunk.mDivX.length);
-        checkDivCount(chunk.mDivY.length);
-
-        // skip 8 bytes
-        byteBuffer.getInt();
-        byteBuffer.getInt();
-
-        chunk.mPaddings.left = byteBuffer.getInt();
-        chunk.mPaddings.right = byteBuffer.getInt();
-        chunk.mPaddings.top = byteBuffer.getInt();
-        chunk.mPaddings.bottom = byteBuffer.getInt();
-
-        // skip 4 bytes
-        byteBuffer.getInt();
-
-        readIntArray(chunk.mDivX, byteBuffer);
-        readIntArray(chunk.mDivY, byteBuffer);
-        readIntArray(chunk.mColor, byteBuffer);
-
-        return chunk;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/ui/NinePatchTexture.java b/src/com/android/gallery3d/ui/NinePatchTexture.java
deleted file mode 100644
index fa0e9cd..0000000
--- a/src/com/android/gallery3d/ui/NinePatchTexture.java
+++ /dev/null
@@ -1,440 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Rect;
-
-import com.android.gallery3d.common.Utils;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import javax.microedition.khronos.opengles.GL11;
-
-// NinePatchTexture is a texture backed by a NinePatch resource.
-//
-// getPaddings() returns paddings specified in the NinePatch.
-// getNinePatchChunk() returns the layout data specified in the NinePatch.
-//
-public class NinePatchTexture extends ResourceTexture {
-    @SuppressWarnings("unused")
-    private static final String TAG = "NinePatchTexture";
-    private NinePatchChunk mChunk;
-    private SmallCache<NinePatchInstance> mInstanceCache
-            = new SmallCache<NinePatchInstance>();
-
-    public NinePatchTexture(Context context, int resId) {
-        super(context, resId);
-    }
-
-    @Override
-    protected Bitmap onGetBitmap() {
-        if (mBitmap != null) return mBitmap;
-
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-        Bitmap bitmap = BitmapFactory.decodeResource(
-                mContext.getResources(), mResId, options);
-        mBitmap = bitmap;
-        setSize(bitmap.getWidth(), bitmap.getHeight());
-        byte[] chunkData = bitmap.getNinePatchChunk();
-        mChunk = chunkData == null
-                ? null
-                : NinePatchChunk.deserialize(bitmap.getNinePatchChunk());
-        if (mChunk == null) {
-            throw new RuntimeException("invalid nine-patch image: " + mResId);
-        }
-        return bitmap;
-    }
-
-    public Rect getPaddings() {
-        // get the paddings from nine patch
-        if (mChunk == null) onGetBitmap();
-        return mChunk.mPaddings;
-    }
-
-    public NinePatchChunk getNinePatchChunk() {
-        if (mChunk == null) onGetBitmap();
-        return mChunk;
-    }
-
-    // This is a simple cache for a small number of things. Linear search
-    // is used because the cache is small. It also tries to remove less used
-    // item when the cache is full by moving the often-used items to the front.
-    private static class SmallCache<V> {
-        private static final int CACHE_SIZE = 16;
-        private static final int CACHE_SIZE_START_MOVE = CACHE_SIZE / 2;
-        private int[] mKey = new int[CACHE_SIZE];
-        private V[] mValue = (V[]) new Object[CACHE_SIZE];
-        private int mCount;  // number of items in this cache
-
-        // Puts a value into the cache. If the cache is full, also returns
-        // a less used item, otherwise returns null.
-        public V put(int key, V value) {
-            if (mCount == CACHE_SIZE) {
-                V old = mValue[CACHE_SIZE - 1];  // remove the last item
-                mKey[CACHE_SIZE - 1] = key;
-                mValue[CACHE_SIZE - 1] = value;
-                return old;
-            } else {
-                mKey[mCount] = key;
-                mValue[mCount] = value;
-                mCount++;
-                return null;
-            }
-        }
-
-        public V get(int key) {
-            for (int i = 0; i < mCount; i++) {
-                if (mKey[i] == key) {
-                    // Move the accessed item one position to the front, so it
-                    // will less likely to be removed when cache is full. Only
-                    // do this if the cache is starting to get full.
-                    if (mCount > CACHE_SIZE_START_MOVE && i > 0) {
-                        int tmpKey = mKey[i];
-                        mKey[i] = mKey[i - 1];
-                        mKey[i - 1] = tmpKey;
-
-                        V tmpValue = mValue[i];
-                        mValue[i] = mValue[i - 1];
-                        mValue[i - 1] = tmpValue;
-                    }
-                    return mValue[i];
-                }
-            }
-            return null;
-        }
-
-        public void clear() {
-            for (int i = 0; i < mCount; i++) {
-                mValue[i] = null;  // make sure it's can be garbage-collected.
-            }
-            mCount = 0;
-        }
-
-        public int size() {
-            return mCount;
-        }
-
-        public V valueAt(int i) {
-            return mValue[i];
-        }
-    }
-
-    private NinePatchInstance findInstance(GLCanvas canvas, int w, int h) {
-        int key = w;
-        key = (key << 16) | h;
-        NinePatchInstance instance = mInstanceCache.get(key);
-
-        if (instance == null) {
-            instance = new NinePatchInstance(this, w, h);
-            NinePatchInstance removed = mInstanceCache.put(key, instance);
-            if (removed != null) {
-                removed.recycle(canvas);
-            }
-        }
-
-        return instance;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int w, int h) {
-        if (!isLoaded()) {
-            mInstanceCache.clear();
-        }
-
-        if (w != 0 && h != 0) {
-            findInstance(canvas, w, h).draw(canvas, this, x, y);
-        }
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        GLCanvas canvas = mCanvasRef;
-        if (canvas == null) return;
-        int n = mInstanceCache.size();
-        for (int i = 0; i < n; i++) {
-            NinePatchInstance instance = mInstanceCache.valueAt(i);
-            instance.recycle(canvas);
-        }
-        mInstanceCache.clear();
-    }
-}
-
-// This keeps data for a specialization of NinePatchTexture with the size
-// (width, height). We pre-compute the coordinates for efficiency.
-class NinePatchInstance {
-
-    @SuppressWarnings("unused")
-    private static final String TAG = "NinePatchInstance";
-
-    // We need 16 vertices for a normal nine-patch image (the 4x4 vertices)
-    private static final int VERTEX_BUFFER_SIZE = 16 * 2;
-
-    // We need 22 indices for a normal nine-patch image, plus 2 for each
-    // transparent region. Current there are at most 1 transparent region.
-    private static final int INDEX_BUFFER_SIZE = 22 + 2;
-
-    private FloatBuffer mXyBuffer;
-    private FloatBuffer mUvBuffer;
-    private ByteBuffer mIndexBuffer;
-
-    // Names for buffer names: xy, uv, index.
-    private int[] mBufferNames;
-
-    private int mIdxCount;
-
-    public NinePatchInstance(NinePatchTexture tex, int width, int height) {
-        NinePatchChunk chunk = tex.getNinePatchChunk();
-
-        if (width <= 0 || height <= 0) {
-            throw new RuntimeException("invalid dimension");
-        }
-
-        // The code should be easily extended to handle the general cases by
-        // allocating more space for buffers. But let's just handle the only
-        // use case.
-        if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
-            throw new RuntimeException("unsupported nine patch");
-        }
-
-        float divX[] = new float[4];
-        float divY[] = new float[4];
-        float divU[] = new float[4];
-        float divV[] = new float[4];
-
-        int nx = stretch(divX, divU, chunk.mDivX, tex.getWidth(), width);
-        int ny = stretch(divY, divV, chunk.mDivY, tex.getHeight(), height);
-
-        prepareVertexData(divX, divY, divU, divV, nx, ny, chunk.mColor);
-    }
-
-    /**
-     * Stretches the texture according to the nine-patch rules. It will
-     * linearly distribute the strechy parts defined in the nine-patch chunk to
-     * the target area.
-     *
-     * <pre>
-     *                      source
-     *          /--------------^---------------\
-     *         u0    u1       u2  u3     u4   u5
-     * div ---> |fffff|ssssssss|fff|ssssss|ffff| ---> u
-     *          |    div0    div1 div2   div3  |
-     *          |     |       /   /      /    /
-     *          |     |      /   /     /    /
-     *          |     |     /   /    /    /
-     *          |fffff|ssss|fff|sss|ffff| ---> x
-     *         x0    x1   x2  x3  x4   x5
-     *          \----------v------------/
-     *                  target
-     *
-     * f: fixed segment
-     * s: stretchy segment
-     * </pre>
-     *
-     * @param div the stretch parts defined in nine-patch chunk
-     * @param source the length of the texture
-     * @param target the length on the drawing plan
-     * @param u output, the positions of these dividers in the texture
-     *        coordinate
-     * @param x output, the corresponding position of these dividers on the
-     *        drawing plan
-     * @return the number of these dividers.
-     */
-    private static int stretch(
-            float x[], float u[], int div[], int source, int target) {
-        int textureSize = Utils.nextPowerOf2(source);
-        float textureBound = (float) source / textureSize;
-
-        float stretch = 0;
-        for (int i = 0, n = div.length; i < n; i += 2) {
-            stretch += div[i + 1] - div[i];
-        }
-
-        float remaining = target - source + stretch;
-
-        float lastX = 0;
-        float lastU = 0;
-
-        x[0] = 0;
-        u[0] = 0;
-        for (int i = 0, n = div.length; i < n; i += 2) {
-            // Make the stretchy segment a little smaller to prevent sampling
-            // on neighboring fixed segments.
-            // fixed segment
-            x[i + 1] = lastX + (div[i] - lastU) + 0.5f;
-            u[i + 1] = Math.min((div[i] + 0.5f) / textureSize, textureBound);
-
-            // stretchy segment
-            float partU = div[i + 1] - div[i];
-            float partX = remaining * partU / stretch;
-            remaining -= partX;
-            stretch -= partU;
-
-            lastX = x[i + 1] + partX;
-            lastU = div[i + 1];
-            x[i + 2] = lastX - 0.5f;
-            u[i + 2] = Math.min((lastU - 0.5f)/ textureSize, textureBound);
-        }
-        // the last fixed segment
-        x[div.length + 1] = target;
-        u[div.length + 1] = textureBound;
-
-        // remove segments with length 0.
-        int last = 0;
-        for (int i = 1, n = div.length + 2; i < n; ++i) {
-            if ((x[i] - x[last]) < 1f) continue;
-            x[++last] = x[i];
-            u[last] = u[i];
-        }
-        return last + 1;
-    }
-
-    private void prepareVertexData(float x[], float y[], float u[], float v[],
-            int nx, int ny, int[] color) {
-        /*
-         * Given a 3x3 nine-patch image, the vertex order is defined as the
-         * following graph:
-         *
-         * (0) (1) (2) (3)
-         *  |  /|  /|  /|
-         *  | / | / | / |
-         * (4) (5) (6) (7)
-         *  | \ | \ | \ |
-         *  |  \|  \|  \|
-         * (8) (9) (A) (B)
-         *  |  /|  /|  /|
-         *  | / | / | / |
-         * (C) (D) (E) (F)
-         *
-         * And we draw the triangle strip in the following index order:
-         *
-         * index: 04152637B6A5948C9DAEBF
-         */
-        int pntCount = 0;
-        float xy[] = new float[VERTEX_BUFFER_SIZE];
-        float uv[] = new float[VERTEX_BUFFER_SIZE];
-        for (int j = 0; j < ny; ++j) {
-            for (int i = 0; i < nx; ++i) {
-                int xIndex = (pntCount++) << 1;
-                int yIndex = xIndex + 1;
-                xy[xIndex] = x[i];
-                xy[yIndex] = y[j];
-                uv[xIndex] = u[i];
-                uv[yIndex] = v[j];
-            }
-        }
-
-        int idxCount = 1;
-        boolean isForward = false;
-        byte index[] = new byte[INDEX_BUFFER_SIZE];
-        for (int row = 0; row < ny - 1; row++) {
-            --idxCount;
-            isForward = !isForward;
-
-            int start, end, inc;
-            if (isForward) {
-                start = 0;
-                end = nx;
-                inc = 1;
-            } else {
-                start = nx - 1;
-                end = -1;
-                inc = -1;
-            }
-
-            for (int col = start; col != end; col += inc) {
-                int k = row * nx + col;
-                if (col != start) {
-                    int colorIdx = row * (nx - 1) + col;
-                    if (isForward) colorIdx--;
-                    if (color[colorIdx] == NinePatchChunk.TRANSPARENT_COLOR) {
-                        index[idxCount] = index[idxCount - 1];
-                        ++idxCount;
-                        index[idxCount++] = (byte) k;
-                    }
-                }
-
-                index[idxCount++] = (byte) k;
-                index[idxCount++] = (byte) (k + nx);
-            }
-        }
-
-        mIdxCount = idxCount;
-
-        int size = (pntCount * 2) * (Float.SIZE / Byte.SIZE);
-        mXyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
-        mUvBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer();
-        mIndexBuffer = allocateDirectNativeOrderBuffer(mIdxCount);
-
-        mXyBuffer.put(xy, 0, pntCount * 2).position(0);
-        mUvBuffer.put(uv, 0, pntCount * 2).position(0);
-        mIndexBuffer.put(index, 0, idxCount).position(0);
-    }
-
-    private static ByteBuffer allocateDirectNativeOrderBuffer(int size) {
-        return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
-    }
-
-    private void prepareBuffers(GLCanvas canvas) {
-        mBufferNames = new int[3];
-        GL11 gl = canvas.getGLInstance();
-        GLId.glGenBuffers(3, mBufferNames, 0);
-
-        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[0]);
-        gl.glBufferData(GL11.GL_ARRAY_BUFFER,
-                mXyBuffer.capacity() * (Float.SIZE / Byte.SIZE),
-                mXyBuffer, GL11.GL_STATIC_DRAW);
-
-        gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[1]);
-        gl.glBufferData(GL11.GL_ARRAY_BUFFER,
-                mUvBuffer.capacity() * (Float.SIZE / Byte.SIZE),
-                mUvBuffer, GL11.GL_STATIC_DRAW);
-
-        gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mBufferNames[2]);
-        gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER,
-                mIndexBuffer.capacity(),
-                mIndexBuffer, GL11.GL_STATIC_DRAW);
-
-        // These buffers are never used again.
-        mXyBuffer = null;
-        mUvBuffer = null;
-        mIndexBuffer = null;
-    }
-
-    public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) {
-        if (mBufferNames == null) {
-            prepareBuffers(canvas);
-        }
-        canvas.drawMesh(tex, x, y, mBufferNames[0], mBufferNames[1],
-                mBufferNames[2], mIdxCount);
-    }
-
-    public void recycle(GLCanvas canvas) {
-        if (mBufferNames != null) {
-            canvas.deleteBuffer(mBufferNames[0]);
-            canvas.deleteBuffer(mBufferNames[1]);
-            canvas.deleteBuffer(mBufferNames[2]);
-            mBufferNames = null;
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/PhotoFallbackEffect.java b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java
index 3ca09ab..4603285 100644
--- a/src/com/android/gallery3d/ui/PhotoFallbackEffect.java
+++ b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java
@@ -23,6 +23,8 @@
 
 import com.android.gallery3d.anim.Animation;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
 import com.android.gallery3d.ui.AlbumSlotRenderer.SlotFilter;
 
 import java.util.ArrayList;
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index 6dcae4c..7afa203 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -35,8 +35,14 @@
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
 import com.android.gallery3d.data.Path;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
+import com.android.gallery3d.glrenderer.ResourceTexture;
+import com.android.gallery3d.glrenderer.StringTexture;
+import com.android.gallery3d.glrenderer.Texture;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.RangeArray;
+import com.android.gallery3d.util.UsageStatistics;
 
 public class PhotoView extends GLView {
     @SuppressWarnings("unused")
@@ -52,7 +58,7 @@
         public int height;
     }
 
-    public interface Model extends TileImageView.Model {
+    public interface Model extends TileImageView.TileSource {
         public int getCurrentIndex();
         public void moveTo(int index);
 
@@ -174,8 +180,9 @@
     public static final int SCREEN_NAIL_MAX = 3;
 
     // These are constants for the delete gesture.
-    private static final int SWIPE_ESCAPE_VELOCITY = 2500; // dp/sec
-    private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec
+    private static final int SWIPE_ESCAPE_VELOCITY = 500; // dp/sec
+    private static final int MAX_DISMISS_VELOCITY = 2500; // dp/sec
+    private static final int SWIPE_ESCAPE_DISTANCE = 150; // dp
 
     // The picture entries, the valid index is from -SCREEN_NAIL_MAX to
     // SCREEN_NAIL_MAX.
@@ -1070,19 +1077,19 @@
         }
 
         @Override
-        public boolean onFling(float velocityX, float velocityY) {
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
             if (mIgnoreSwipingGesture) return true;
             if (mModeChanged) return true;
             if (swipeImages(velocityX, velocityY)) {
                 mIgnoreUpEvent = true;
             } else {
-                flingImages(velocityX, velocityY);
+                flingImages(velocityX, velocityY, Math.abs(e2.getY() - e1.getY()));
             }
             mHadFling = true;
             return true;
         }
 
-        private boolean flingImages(float velocityX, float velocityY) {
+        private boolean flingImages(float velocityX, float velocityY, float dY) {
             int vx = (int) (velocityX + 0.5f);
             int vy = (int) (velocityY + 0.5f);
             if (!mFilmMode) {
@@ -1099,11 +1106,13 @@
             }
             int maxVelocity = GalleryUtils.dpToPixel(MAX_DISMISS_VELOCITY);
             int escapeVelocity = GalleryUtils.dpToPixel(SWIPE_ESCAPE_VELOCITY);
+            int escapeDistance = GalleryUtils.dpToPixel(SWIPE_ESCAPE_DISTANCE);
             int centerY = mPositionController.getPosition(mTouchBoxIndex)
                     .centerY();
             boolean fastEnough = (Math.abs(vy) > escapeVelocity)
                     && (Math.abs(vy) > Math.abs(vx))
-                    && ((vy > 0) == (centerY > getHeight() / 2));
+                    && ((vy > 0) == (centerY > getHeight() / 2))
+                    && dY >= escapeDistance;
             if (fastEnough) {
                 vy = Math.min(vy, maxVelocity);
                 int duration = mPositionController.flingFilmY(mTouchBoxIndex, vy);
@@ -1172,8 +1181,16 @@
                     // Removing the touch down flag allows snapback to happen
                     // for film mode change.
                     mHolding &= ~HOLD_TOUCH_DOWN;
+                    if (mFilmMode) {
+                        UsageStatistics.setPendingTransitionCause(
+                                UsageStatistics.TRANSITION_PINCH_OUT);
+                    } else {
+                        UsageStatistics.setPendingTransitionCause(
+                                UsageStatistics.TRANSITION_PINCH_IN);
+                    }
                     setFilmMode(!mFilmMode);
 
+
                     // We need to call onScaleEnd() before setting mModeChanged
                     // to true.
                     onScaleEnd();
@@ -1237,7 +1254,10 @@
             if (mFilmMode) {
                 int xi = (int) (x + 0.5f);
                 int yi = (int) (y + 0.5f);
-                mTouchBoxIndex = mPositionController.hitTest(xi, yi);
+                // We only care about being within the x bounds, necessary for
+                // handling very wide images which are otherwise very hard to fling
+                mTouchBoxIndex = mPositionController.hitTest(xi, getHeight() / 2);
+
                 if (mTouchBoxIndex < mPrevBound || mTouchBoxIndex > mNextBound) {
                     mTouchBoxIndex = Integer.MAX_VALUE;
                 } else {
@@ -1403,6 +1423,11 @@
 
     @Override
     protected void render(GLCanvas canvas) {
+        if (mFirst) {
+            // Make sure the fields are properly initialized before checking
+            // whether isCamera()
+            mPictures.get(0).reload();
+        }
         // Check if the camera preview occupies the full screen.
         boolean full = !mFilmMode && mPictures.get(0).isCamera()
                 && mPositionController.isCenter()
diff --git a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java
index 36e7f4b..f52aa5f 100644
--- a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java
+++ b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java
@@ -3,6 +3,8 @@
 import android.os.ConditionVariable;
 
 import com.android.gallery3d.app.AbstractGalleryActivity;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.RawTexture;
 import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
 
 public class PreparePageFadeoutTexture implements OnGLIdleListener {
diff --git a/src/com/android/gallery3d/ui/ProgressSpinner.java b/src/com/android/gallery3d/ui/ProgressSpinner.java
index 4f9381c..1b31af2 100644
--- a/src/com/android/gallery3d/ui/ProgressSpinner.java
+++ b/src/com/android/gallery3d/ui/ProgressSpinner.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.ResourceTexture;
 
 public class ProgressSpinner {
     private static float ROTATE_SPEED_OUTER = 1080f / 3500f;
diff --git a/src/com/android/gallery3d/ui/RawTexture.java b/src/com/android/gallery3d/ui/RawTexture.java
deleted file mode 100644
index 4c0d9d3..0000000
--- a/src/com/android/gallery3d/ui/RawTexture.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11Ext;
-
-public class RawTexture extends BasicTexture {
-    private static final String TAG = "RawTexture";
-
-    private final static int[] sTextureId = new int[1];
-    private final static float[] sCropRect = new float[4];
-
-    private final boolean mOpaque;
-
-    public RawTexture(int width, int height, boolean opaque) {
-        mOpaque = opaque;
-        setSize(width, height);
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return mOpaque;
-    }
-
-    protected void prepare(GLCanvas canvas) {
-        GL11 gl = canvas.getGLInstance();
-
-        // Define a vertically flipped crop rectangle for
-        // OES_draw_texture.
-        // The four values in sCropRect are: left, bottom, width, and
-        // height. Negative value of width or height means flip.
-        sCropRect[0] = 0;
-        sCropRect[1] = mHeight;
-        sCropRect[2] = mWidth;
-        sCropRect[3] = -mHeight;
-
-        // Upload the bitmap to a new texture.
-        GLId.glGenTextures(1, sTextureId, 0);
-        gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
-        gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
-                GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
-        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
-                GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
-        gl.glTexParameteri(GL11.GL_TEXTURE_2D,
-                GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
-        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
-                GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
-        gl.glTexParameterf(GL11.GL_TEXTURE_2D,
-                GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
-
-        gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA,
-                getTextureWidth(), getTextureHeight(),
-                0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null);
-
-        mId = sTextureId[0];
-        mState = STATE_LOADED;
-        setAssociatedCanvas(canvas);
-    }
-
-    @Override
-    protected boolean onBind(GLCanvas canvas) {
-        if (isLoaded()) return true;
-        Log.w(TAG, "lost the content due to context change");
-        return false;
-    }
-
-    @Override
-     public void yield() {
-         // we cannot free the texture because we have no backup.
-     }
-
-    @Override
-    protected int getTarget() {
-        return GL11.GL_TEXTURE_2D;
-    }
-}
diff --git a/src/com/android/gallery3d/ui/ResourceTexture.java b/src/com/android/gallery3d/ui/ResourceTexture.java
deleted file mode 100644
index 1fa9d70..0000000
--- a/src/com/android/gallery3d/ui/ResourceTexture.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
-import com.android.gallery3d.common.Utils;
-
-// ResourceTexture is a texture whose Bitmap is decoded from a resource.
-// By default ResourceTexture is not opaque.
-public class ResourceTexture extends UploadedTexture {
-
-    protected final Context mContext;
-    protected final int mResId;
-
-    public ResourceTexture(Context context, int resId) {
-        mContext = Utils.checkNotNull(context);
-        mResId = resId;
-        setOpaque(false);
-    }
-
-    @Override
-    protected Bitmap onGetBitmap() {
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-        return BitmapFactory.decodeResource(
-                mContext.getResources(), mResId, options);
-    }
-
-    @Override
-    protected void onFreeBitmap(Bitmap bitmap) {
-        if (!inFinalizer()) {
-            bitmap.recycle();
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/ScreenNail.java b/src/com/android/gallery3d/ui/ScreenNail.java
index 0a16ab8..965bf0b 100644
--- a/src/com/android/gallery3d/ui/ScreenNail.java
+++ b/src/com/android/gallery3d/ui/ScreenNail.java
@@ -17,6 +17,8 @@
 
 import android.graphics.RectF;
 
+import com.android.gallery3d.glrenderer.GLCanvas;
+
 public interface ScreenNail {
     public int getWidth();
     public int getHeight();
diff --git a/src/com/android/gallery3d/ui/ScrollBarView.java b/src/com/android/gallery3d/ui/ScrollBarView.java
index 82d4800..34fbcef 100644
--- a/src/com/android/gallery3d/ui/ScrollBarView.java
+++ b/src/com/android/gallery3d/ui/ScrollBarView.java
@@ -20,6 +20,9 @@
 import android.graphics.Rect;
 import android.util.TypedValue;
 
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.NinePatchTexture;
+
 public class ScrollBarView extends GLView {
     @SuppressWarnings("unused")
     private static final String TAG = "ScrollBarView";
diff --git a/src/com/android/gallery3d/ui/SelectionManager.java b/src/com/android/gallery3d/ui/SelectionManager.java
index a6be0dd..be6811b 100644
--- a/src/com/android/gallery3d/ui/SelectionManager.java
+++ b/src/com/android/gallery3d/ui/SelectionManager.java
@@ -146,10 +146,12 @@
         }
     }
 
-    private static void expandMediaSet(ArrayList<Path> items, MediaSet set) {
+    private static boolean expandMediaSet(ArrayList<Path> items, MediaSet set, int maxSelection) {
         int subCount = set.getSubMediaSetCount();
         for (int i = 0; i < subCount; i++) {
-            expandMediaSet(items, set.getSubMediaSet(i));
+            if (!expandMediaSet(items, set.getSubMediaSet(i), maxSelection)) {
+                return false;
+            }
         }
         int total = set.getMediaItemCount();
         int batch = 50;
@@ -160,14 +162,23 @@
                     ? batch
                     : total - index;
             ArrayList<MediaItem> list = set.getMediaItem(index, count);
+            if (list != null
+                    && list.size() > (maxSelection - items.size())) {
+                return false;
+            }
             for (MediaItem item : list) {
                 items.add(item.getPath());
             }
             index += batch;
         }
+        return true;
     }
 
     public ArrayList<Path> getSelected(boolean expandSet) {
+        return getSelected(expandSet, Integer.MAX_VALUE);
+    }
+
+    public ArrayList<Path> getSelected(boolean expandSet, int maxSelection) {
         ArrayList<Path> selected = new ArrayList<Path>();
         if (mIsAlbumSet) {
             if (mInverseSelection) {
@@ -177,18 +188,29 @@
                     Path id = set.getPath();
                     if (!mClickedSet.contains(id)) {
                         if (expandSet) {
-                            expandMediaSet(selected, set);
+                            if (!expandMediaSet(selected, set, maxSelection)) {
+                                return null;
+                            }
                         } else {
                             selected.add(id);
+                            if (selected.size() > maxSelection) {
+                                return null;
+                            }
                         }
                     }
                 }
             } else {
                 for (Path id : mClickedSet) {
                     if (expandSet) {
-                        expandMediaSet(selected, mDataManager.getMediaSet(id));
+                        if (!expandMediaSet(selected, mDataManager.getMediaSet(id),
+                                maxSelection)) {
+                            return null;
+                        }
                     } else {
                         selected.add(id);
+                        if (selected.size() > maxSelection) {
+                            return null;
+                        }
                     }
                 }
             }
@@ -201,13 +223,21 @@
                     ArrayList<MediaItem> list = mSourceMediaSet.getMediaItem(index, count);
                     for (MediaItem item : list) {
                         Path id = item.getPath();
-                        if (!mClickedSet.contains(id)) selected.add(id);
+                        if (!mClickedSet.contains(id)) {
+                            selected.add(id);
+                            if (selected.size() > maxSelection) {
+                                return null;
+                            }
+                        }
                     }
                     index += count;
                 }
             } else {
                 for (Path id : mClickedSet) {
                     selected.add(id);
+                    if (selected.size() > maxSelection) {
+                        return null;
+                    }
                 }
             }
         }
diff --git a/src/com/android/gallery3d/ui/SlideshowView.java b/src/com/android/gallery3d/ui/SlideshowView.java
index bb36c47..4378423 100644
--- a/src/com/android/gallery3d/ui/SlideshowView.java
+++ b/src/com/android/gallery3d/ui/SlideshowView.java
@@ -21,11 +21,11 @@
 
 import com.android.gallery3d.anim.CanvasAnimation;
 import com.android.gallery3d.anim.FloatAnimation;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 import java.util.Random;
 
-import javax.microedition.khronos.opengles.GL11;
-
 public class SlideshowView extends GLView {
     @SuppressWarnings("unused")
     private static final String TAG = "SlideshowView";
@@ -93,8 +93,6 @@
     protected void render(GLCanvas canvas) {
         long animTime = AnimationTime.get();
         boolean requestRender = mTransitionAnimation.calculate(animTime);
-        GL11 gl = canvas.getGLInstance();
-        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
         float alpha = mPrevTexture == null ? 1f : mTransitionAnimation.get();
 
         if (mPrevTexture != null && alpha != 1f) {
@@ -118,7 +116,6 @@
             canvas.restore();
         }
         if (requestRender) invalidate();
-        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
     }
 
     private class SlideshowAnimation extends CanvasAnimation {
diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java
index 15a583e..bd0ffdc 100644
--- a/src/com/android/gallery3d/ui/SlotView.java
+++ b/src/com/android/gallery3d/ui/SlotView.java
@@ -25,6 +25,7 @@
 import com.android.gallery3d.anim.Animation;
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 public class SlotView extends GLView {
     @SuppressWarnings("unused")
diff --git a/src/com/android/gallery3d/ui/StringTexture.java b/src/com/android/gallery3d/ui/StringTexture.java
deleted file mode 100644
index 97995c8..0000000
--- a/src/com/android/gallery3d/ui/StringTexture.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint.FontMetricsInt;
-import android.graphics.Typeface;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.FloatMath;
-
-// StringTexture is a texture shows the content of a specified String.
-//
-// To create a StringTexture, use the newInstance() method and specify
-// the String, the font size, and the color.
-class StringTexture extends CanvasTexture {
-    private final String mText;
-    private final TextPaint mPaint;
-    private final FontMetricsInt mMetrics;
-
-    private StringTexture(String text, TextPaint paint,
-            FontMetricsInt metrics, int width, int height) {
-        super(width, height);
-        mText = text;
-        mPaint = paint;
-        mMetrics = metrics;
-    }
-
-    public static TextPaint getDefaultPaint(float textSize, int color) {
-        TextPaint paint = new TextPaint();
-        paint.setTextSize(textSize);
-        paint.setAntiAlias(true);
-        paint.setColor(color);
-        paint.setShadowLayer(2f, 0f, 0f, Color.BLACK);
-        return paint;
-    }
-
-    public static StringTexture newInstance(
-            String text, float textSize, int color) {
-        return newInstance(text, getDefaultPaint(textSize, color));
-    }
-
-    public static StringTexture newInstance(
-            String text, float textSize, int color,
-            float lengthLimit, boolean isBold) {
-        TextPaint paint = getDefaultPaint(textSize, color);
-        if (isBold) {
-            paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
-        }
-        if (lengthLimit > 0) {
-            text = TextUtils.ellipsize(
-                    text, paint, lengthLimit, TextUtils.TruncateAt.END).toString();
-        }
-        return newInstance(text, paint);
-    }
-
-    private static StringTexture newInstance(String text, TextPaint paint) {
-        FontMetricsInt metrics = paint.getFontMetricsInt();
-        int width = (int) FloatMath.ceil(paint.measureText(text));
-        int height = metrics.bottom - metrics.top;
-        // The texture size needs to be at least 1x1.
-        if (width <= 0) width = 1;
-        if (height <= 0) height = 1;
-        return new StringTexture(text, paint, metrics, width, height);
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas, Bitmap backing) {
-        canvas.translate(0, -mMetrics.ascent);
-        canvas.drawText(mText, 0, 0, mPaint);
-    }
-}
diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
index ceed71a..18121e6 100644
--- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
+++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java
@@ -21,6 +21,8 @@
 import android.graphics.SurfaceTexture;
 
 import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.glrenderer.ExtTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
 
 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
 public abstract class SurfaceTextureScreenNail implements ScreenNail,
@@ -40,8 +42,8 @@
     public SurfaceTextureScreenNail() {
     }
 
-    public void acquireSurfaceTexture() {
-        mExtTexture = new ExtTexture(GL_TEXTURE_EXTERNAL_OES);
+    public void acquireSurfaceTexture(GLCanvas canvas) {
+        mExtTexture = new ExtTexture(canvas, GL_TEXTURE_EXTERNAL_OES);
         mExtTexture.setSize(mWidth, mHeight);
         mSurfaceTexture = new SurfaceTexture(mExtTexture.getId());
         setDefaultBufferSize(mSurfaceTexture, mWidth, mHeight);
diff --git a/src/com/android/gallery3d/ui/Texture.java b/src/com/android/gallery3d/ui/Texture.java
deleted file mode 100644
index 2c426f9..0000000
--- a/src/com/android/gallery3d/ui/Texture.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-// Texture is a rectangular image which can be drawn on GLCanvas.
-// The isOpaque() function gives a hint about whether the texture is opaque,
-// so the drawing can be done faster.
-//
-// This is the current texture hierarchy:
-//
-// Texture
-// -- ColorTexture
-// -- FadeInTexture
-// -- BasicTexture
-//    -- UploadedTexture
-//       -- BitmapTexture
-//       -- Tile
-//       -- ResourceTexture
-//          -- NinePatchTexture
-//       -- CanvasTexture
-//          -- StringTexture
-//
-public interface Texture {
-    public int getWidth();
-    public int getHeight();
-    public void draw(GLCanvas canvas, int x, int y);
-    public void draw(GLCanvas canvas, int x, int y, int w, int h);
-    public boolean isOpaque();
-}
diff --git a/src/com/android/gallery3d/ui/TextureUploader.java b/src/com/android/gallery3d/ui/TextureUploader.java
deleted file mode 100644
index 9a8c47f..0000000
--- a/src/com/android/gallery3d/ui/TextureUploader.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.ui;
-
-import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
-
-import java.util.ArrayDeque;
-
-public class TextureUploader implements OnGLIdleListener {
-    private static final int INIT_CAPACITY = 64;
-    private static final int QUOTA_PER_FRAME = 1;
-
-    private final ArrayDeque<UploadedTexture> mFgTextures =
-            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
-    private final ArrayDeque<UploadedTexture> mBgTextures =
-            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
-    private final GLRoot mGLRoot;
-    private volatile boolean mIsQueued = false;
-
-    public TextureUploader(GLRoot root) {
-        mGLRoot = root;
-    }
-
-    public synchronized void clear() {
-        while (!mFgTextures.isEmpty()) {
-            mFgTextures.pop().setIsUploading(false);
-        }
-        while (!mBgTextures.isEmpty()) {
-            mBgTextures.pop().setIsUploading(false);
-        }
-    }
-
-    // caller should hold synchronized on "this"
-    private void queueSelfIfNeed() {
-        if (mIsQueued) return;
-        mIsQueued = true;
-        mGLRoot.addOnGLIdleListener(this);
-    }
-
-    public synchronized void addBgTexture(UploadedTexture t) {
-        if (t.isContentValid()) return;
-        mBgTextures.addLast(t);
-        t.setIsUploading(true);
-        queueSelfIfNeed();
-    }
-
-    public synchronized void addFgTexture(UploadedTexture t) {
-        if (t.isContentValid()) return;
-        mFgTextures.addLast(t);
-        t.setIsUploading(true);
-        queueSelfIfNeed();
-    }
-
-    private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
-            int uploadQuota, boolean isBackground) {
-        while (uploadQuota > 0) {
-            UploadedTexture t;
-            synchronized (this) {
-                if (deque.isEmpty()) break;
-                t = deque.removeFirst();
-                t.setIsUploading(false);
-                if (t.isContentValid()) continue;
-
-                // this has to be protected by the synchronized block
-                // to prevent the inner bitmap get recycled
-                t.updateContent(canvas);
-            }
-
-            // It will took some more time for a texture to be drawn for
-            // the first time.
-            // Thus, when scrolling, if a new column appears on screen,
-            // it may cause a UI jank even these textures are uploaded.
-            if (isBackground) t.draw(canvas, 0, 0);
-            --uploadQuota;
-        }
-        return uploadQuota;
-    }
-
-    @Override
-    public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
-        int uploadQuota = QUOTA_PER_FRAME;
-        uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
-        if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
-        upload(canvas, mBgTextures, uploadQuota, true);
-        synchronized (this) {
-            mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
-            return mIsQueued;
-        }
-    }
-}
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index 8f26981..3185c75 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -16,20 +16,23 @@
 
 package com.android.gallery3d.ui;
 
+import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.support.v4.util.LongSparseArray;
+import android.util.DisplayMetrics;
 import android.util.FloatMath;
+import android.view.WindowManager;
 
 import com.android.gallery3d.app.GalleryContext;
-import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.common.LongSparseArray;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DecodeUtils;
+import com.android.photos.data.GalleryBitmapPool;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
 import com.android.gallery3d.util.Future;
-import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -41,15 +44,10 @@
 
     @SuppressWarnings("unused")
     private static final String TAG = "TileImageView";
-
-    // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the
-    // texture to avoid seams between tiles.
-    private static int TILE_SIZE;
-    private static final int TILE_BORDER = 1;
-    private static int BITMAP_SIZE;
     private static final int UPLOAD_LIMIT = 1;
 
-    private static BitmapPool sTilePool;
+    // TILE_SIZE must be 2^N
+    private static int sTileSize;
 
     /*
      *  This is the tile state in the CPU side.
@@ -76,7 +74,7 @@
     private static final int STATE_RECYCLING = 0x20;
     private static final int STATE_RECYCLED = 0x40;
 
-    private Model mModel;
+    private TileSource mModel;
     private ScreenNail mScreenNail;
     protected int mLevelCount;  // cache the value of mScaledBitmaps.length
 
@@ -125,7 +123,7 @@
     private final ThreadPool mThreadPool;
     private boolean mBackgroundTileUploaded;
 
-    public static interface Model {
+    public static interface TileSource {
         public int getLevelCount();
         public ScreenNail getScreenNail();
         public int getImageWidth();
@@ -133,8 +131,7 @@
 
         // The tile returned by this method can be specified this way: Assuming
         // the image size is (width, height), first take the intersection of (0,
-        // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then
-        // extend this intersection region by borderSize pixels on each side. If
+        // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
         // in extending the region, we found some part of the region are outside
         // the image, those pixels are filled with black.
         //
@@ -143,28 +140,30 @@
         // still refers to the coordinate on the original image.
         //
         // The method would be called in another thread.
-        public Bitmap getTile(int level, int x, int y, int tileSize,
-                int borderSize, BitmapPool pool);
+        public Bitmap getTile(int level, int x, int y, int tileSize);
+    }
+
+    public static boolean isHighResolution(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics.heightPixels > 2048 ||  metrics.widthPixels > 2048;
     }
 
     public TileImageView(GalleryContext context) {
         mThreadPool = context.getThreadPool();
         mTileDecoder = mThreadPool.submit(new TileDecoder());
-        if (TILE_SIZE == 0) {
-            if (GalleryUtils.isHighResolution(context.getAndroidContext())) {
-                TILE_SIZE = 510 ;
+        if (sTileSize == 0) {
+            if (isHighResolution(context.getAndroidContext())) {
+                sTileSize = 512 ;
             } else {
-                TILE_SIZE = 254;
+                sTileSize = 256;
             }
-            BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2;
-            sTilePool =
-                    ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
-                    ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128)
-                    : null;
         }
     }
 
-    public void setModel(Model model) {
+    public void setModel(TileSource model) {
         mModel = model;
         if (model != null) notifyModelInvalidated();
     }
@@ -266,7 +265,7 @@
         }
 
         for (int i = fromLevel; i < endLevel; ++i) {
-            int size = TILE_SIZE << i;
+            int size = sTileSize << i;
             Rect r = range[i - fromLevel];
             for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
                 for (int x = r.left, right = r.right; x < right; x += size) {
@@ -320,7 +319,7 @@
         int bottom = (int) FloatMath.ceil(top + height / scale);
 
         // align the rectangle to tile boundary
-        int size = TILE_SIZE << level;
+        int size = sTileSize << level;
         left = Math.max(0, size * (left / size));
         top = Math.max(0, size * (top / size));
         right = Math.min(mImageWidth, right);
@@ -392,7 +391,6 @@
             }
         }
         setScreenNail(null);
-        if (sTilePool != null) sTilePool.clear();
     }
 
     public void prepareTextures() {
@@ -431,7 +429,7 @@
                     mScreenNail.noDraw();
                 }
 
-                int size = (TILE_SIZE << level);
+                int size = (sTileSize << level);
                 float length = size * mScale;
                 Rect r = mTileRange;
 
@@ -501,7 +499,7 @@
             if (tile.mTileState == STATE_RECYCLING) {
                 tile.mTileState = STATE_RECYCLED;
                 if (tile.mDecodedTile != null) {
-                    if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+                    GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
                     tile.mDecodedTile = null;
                 }
                 mRecycledQueue.push(tile);
@@ -529,7 +527,7 @@
         }
         tile.mTileState = STATE_RECYCLED;
         if (tile.mDecodedTile != null) {
-            if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+            GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
             tile.mDecodedTile = null;
         }
         mRecycledQueue.push(tile);
@@ -594,7 +592,7 @@
         RectF source = mSourceRect;
         RectF target = mTargetRect;
         target.set(x, y, x + length, y + length);
-        source.set(0, 0, TILE_SIZE, TILE_SIZE);
+        source.set(0, 0, sTileSize, sTileSize);
 
         Tile tile = getTile(tx, ty, level);
         if (tile != null) {
@@ -614,7 +612,7 @@
             if (drawTile(tile, canvas, source, target)) return;
         }
         if (mScreenNail != null) {
-            int size = TILE_SIZE << level;
+            int size = sTileSize << level;
             float scaleX = (float) mScreenNail.getWidth() / mImageWidth;
             float scaleY = (float) mScreenNail.getHeight() / mImageHeight;
             source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX,
@@ -627,8 +625,6 @@
             Tile tile, GLCanvas canvas, RectF source, RectF target) {
         while (true) {
             if (tile.isContentValid()) {
-                // offset source rectangle for the texture border.
-                source.offset(TILE_BORDER, TILE_BORDER);
                 canvas.drawTexture(tile, source, target);
                 return true;
             }
@@ -640,15 +636,15 @@
                 source.left /= 2f;
                 source.right /= 2f;
             } else {
-                source.left = (TILE_SIZE + source.left) / 2f;
-                source.right = (TILE_SIZE + source.right) / 2f;
+                source.left = (sTileSize + source.left) / 2f;
+                source.right = (sTileSize + source.right) / 2f;
             }
             if (tile.mY == parent.mY) {
                 source.top /= 2f;
                 source.bottom /= 2f;
             } else {
-                source.top = (TILE_SIZE + source.top) / 2f;
-                source.bottom = (TILE_SIZE + source.bottom) / 2f;
+                source.top = (sTileSize + source.top) / 2f;
+                source.bottom = (sTileSize + source.bottom) / 2f;
             }
             tile = parent;
         }
@@ -670,7 +666,7 @@
 
         @Override
         protected void onFreeBitmap(Bitmap bitmap) {
-            if (sTilePool != null) sTilePool.recycle(bitmap);
+            GalleryBitmapPool.getInstance().put(bitmap);
         }
 
         boolean decode() {
@@ -678,7 +674,7 @@
             // by (1 << mTilelevel) from a region in the original image.
             try {
                 mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(
-                        mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER, sTilePool));
+                        mTileLevel, mX, mY, sTileSize));
             } catch (Throwable t) {
                 Log.w(TAG, "fail to decode tile", t);
             }
@@ -691,9 +687,9 @@
 
             // We need to override the width and height, so that we won't
             // draw beyond the boundaries.
-            int rightEdge = ((mImageWidth - mX) >> mTileLevel) + TILE_BORDER;
-            int bottomEdge = ((mImageHeight - mY) >> mTileLevel) + TILE_BORDER;
-            setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge));
+            int rightEdge = ((mImageWidth - mX) >> mTileLevel);
+            int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
+            setSize(Math.min(sTileSize, rightEdge), Math.min(sTileSize, bottomEdge));
 
             Bitmap bitmap = mDecodedTile;
             mDecodedTile = null;
@@ -707,12 +703,12 @@
         // boundary).
         @Override
         public int getTextureWidth() {
-            return TILE_SIZE + TILE_BORDER * 2;
+            return sTileSize;
         }
 
         @Override
         public int getTextureHeight() {
-            return TILE_SIZE + TILE_BORDER * 2;
+            return sTileSize;
         }
 
         public void update(int x, int y, int level) {
@@ -724,7 +720,7 @@
 
         public Tile getParentTile() {
             if (mTileLevel + 1 == mLevelCount) return null;
-            int size = TILE_SIZE << (mTileLevel + 1);
+            int size = sTileSize << (mTileLevel + 1);
             int x = size * (mX / size);
             int y = size * (mY / size);
             return getTile(x, y, mTileLevel + 1);
@@ -733,7 +729,7 @@
         @Override
         public String toString() {
             return String.format("tile(%s, %s, %s / %s)",
-                    mX / TILE_SIZE, mY / TILE_SIZE, mLevel, mLevelCount);
+                    mX / sTileSize, mY / sTileSize, mLevel, mLevelCount);
         }
     }
 
diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
index 45e2ce2..0c1f66d 100644
--- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java
+++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java
@@ -26,9 +26,9 @@
 
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
-public class TileImageViewAdapter implements TileImageView.Model {
+public class TileImageViewAdapter implements TileImageView.TileSource {
     private static final String TAG = "TileImageViewAdapter";
     protected ScreenNail mScreenNail;
     protected boolean mOwnScreenNail;
@@ -84,16 +84,14 @@
     // (44, 44, 256, 256) from the original photo and down sample it to 106.
     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize,
-            int borderSize, BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
-            return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize);
+            return getTileWithoutReusingBitmap(level, x, y, tileSize);
         }
 
-        int b = borderSize << level;
         int t = tileSize << level;
 
-        Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
+        Rect wantRegion = new Rect(x, y, x + t, y + t);
 
         boolean needClear;
         BitmapRegionDecoder regionDecoder = null;
@@ -108,12 +106,11 @@
                     .contains(wantRegion);
         }
 
-        Bitmap bitmap = pool == null ? null : pool.getBitmap();
+        Bitmap bitmap = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (bitmap != null) {
             if (needClear) bitmap.eraseColor(0);
         } else {
-            int s = tileSize + 2 * borderSize;
-            bitmap = Bitmap.createBitmap(s, s, Config.ARGB_8888);
+            bitmap = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
         }
 
         BitmapFactory.Options options = new BitmapFactory.Options();
@@ -129,7 +126,7 @@
             }
         } finally {
             if (options.inBitmap != bitmap && options.inBitmap != null) {
-                if (pool != null) pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
         }
@@ -141,10 +138,9 @@
     }
 
     private Bitmap getTileWithoutReusingBitmap(
-            int level, int x, int y, int tileSize, int borderSize) {
-        int b = borderSize << level;
+            int level, int x, int y, int tileSize) {
         int t = tileSize << level;
-        Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b);
+        Rect wantRegion = new Rect(x, y, x + t, y + t);
 
         BitmapRegionDecoder regionDecoder;
         Rect overlapRegion;
@@ -173,8 +169,7 @@
 
         if (wantRegion.equals(overlapRegion)) return bitmap;
 
-        int s = tileSize + 2 * borderSize;
-        Bitmap result = Bitmap.createBitmap(s, s, Config.ARGB_8888);
+        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
         Canvas canvas = new Canvas(result);
         canvas.drawBitmap(bitmap,
                 (overlapRegion.left - wantRegion.left) >> level,
diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java
index 74665f5..860e230 100644
--- a/src/com/android/gallery3d/ui/TiledScreenNail.java
+++ b/src/com/android/gallery3d/ui/TiledScreenNail.java
@@ -20,8 +20,9 @@
 import android.graphics.RectF;
 
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
-import com.android.gallery3d.data.MediaItem;
+import com.android.photos.data.GalleryBitmapPool;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.TiledTexture;
 
 // This is a ScreenNail wraps a Bitmap. There are some extra functions:
 //
@@ -81,11 +82,6 @@
         mHeight = Math.round(scale * height);
     }
 
-    private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) {
-        if (pool == null || bitmap == null) return;
-        pool.recycle(bitmap);
-    }
-
     // Combines the two ScreenNails.
     // Returns the used one and recycle the unused one.
     public ScreenNail combine(ScreenNail other) {
@@ -104,7 +100,7 @@
         mWidth = newer.mWidth;
         mHeight = newer.mHeight;
         if (newer.mTexture != null) {
-            recycleBitmap(MediaItem.getThumbPool(), mBitmap);
+            if (mBitmap != null) GalleryBitmapPool.getInstance().put(mBitmap);
             if (mTexture != null) mTexture.recycle();
             mBitmap = newer.mBitmap;
             mTexture = newer.mTexture;
@@ -141,8 +137,10 @@
             mTexture.recycle();
             mTexture = null;
         }
-        recycleBitmap(MediaItem.getThumbPool(), mBitmap);
-        mBitmap = null;
+        if (mBitmap != null) {
+            GalleryBitmapPool.getInstance().put(mBitmap);
+            mBitmap = null;
+        }
     }
 
     public static void disableDrawPlaceholder() {
diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/ui/TiledTexture.java
deleted file mode 100644
index 02bde9f..0000000
--- a/src/com/android/gallery3d/ui/TiledTexture.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2012 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.RectF;
-import android.os.SystemClock;
-
-import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-
-// This class is similar to BitmapTexture, except the bitmap is
-// split into tiles. By doing so, we may increase the time required to
-// upload the whole bitmap but we reduce the time of uploading each tile
-// so it make the animation more smooth and prevents jank.
-public class TiledTexture implements Texture {
-    private static final int CONTENT_SIZE = 254;
-    private static final int BORDER_SIZE = 1;
-    private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE;
-    private static final int INIT_CAPACITY = 8;
-
-    // We are targeting at 60fps, so we have 16ms for each frame.
-    // In this 16ms, we use about 4~8 ms to upload tiles.
-    private static final long UPLOAD_TILE_LIMIT = 4; // ms
-
-    private static Tile sFreeTileHead = null;
-    private static final Object sFreeTileLock = new Object();
-
-    private static Bitmap sUploadBitmap;
-    private static Canvas sCanvas;
-    private static Paint sBitmapPaint;
-    private static Paint sPaint;
-
-    private int mUploadIndex = 0;
-
-    private final Tile[] mTiles;  // Can be modified in different threads.
-                                  // Should be protected by "synchronized."
-    private final int mWidth;
-    private final int mHeight;
-    private final RectF mSrcRect = new RectF();
-    private final RectF mDestRect = new RectF();
-
-    public static class Uploader implements OnGLIdleListener {
-        private final ArrayDeque<TiledTexture> mTextures =
-                new ArrayDeque<TiledTexture>(INIT_CAPACITY);
-
-        private final GLRoot mGlRoot;
-        private boolean mIsQueued = false;
-
-        public Uploader(GLRoot glRoot) {
-            mGlRoot = glRoot;
-        }
-
-        public synchronized void clear() {
-            mTextures.clear();
-        }
-
-        public synchronized void addTexture(TiledTexture t) {
-            if (t.isReady()) return;
-            mTextures.addLast(t);
-
-            if (mIsQueued) return;
-            mIsQueued = true;
-            mGlRoot.addOnGLIdleListener(this);
-        }
-
-        @Override
-        public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
-            ArrayDeque<TiledTexture> deque = mTextures;
-            synchronized (this) {
-                long now = SystemClock.uptimeMillis();
-                long dueTime = now + UPLOAD_TILE_LIMIT;
-                while (now < dueTime && !deque.isEmpty()) {
-                    TiledTexture t = deque.peekFirst();
-                    if (t.uploadNextTile(canvas)) {
-                        deque.removeFirst();
-                        mGlRoot.requestRender();
-                    }
-                    now = SystemClock.uptimeMillis();
-                }
-                mIsQueued = !mTextures.isEmpty();
-
-                // return true to keep this listener in the queue
-                return mIsQueued;
-            }
-        }
-    }
-
-    private static class Tile extends UploadedTexture {
-        public int offsetX;
-        public int offsetY;
-        public Bitmap bitmap;
-        public Tile nextFreeTile;
-        public int contentWidth;
-        public int contentHeight;
-
-        @Override
-        public void setSize(int width, int height) {
-            contentWidth = width;
-            contentHeight = height;
-            mWidth = width + 2 * BORDER_SIZE;
-            mHeight = height + 2 * BORDER_SIZE;
-            mTextureWidth = TILE_SIZE;
-            mTextureHeight = TILE_SIZE;
-        }
-
-        @Override
-        protected Bitmap onGetBitmap() {
-            int x = BORDER_SIZE - offsetX;
-            int y = BORDER_SIZE - offsetY;
-            int r = bitmap.getWidth() + x;
-            int b = bitmap.getHeight() + y;
-            sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint);
-            bitmap = null;
-
-            // draw borders if need
-            if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint);
-            if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint);
-            if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint);
-            if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint);
-
-            return sUploadBitmap;
-        }
-
-        @Override
-        protected void onFreeBitmap(Bitmap bitmap) {
-            // do nothing
-        }
-    }
-
-    private static void freeTile(Tile tile) {
-        tile.invalidateContent();
-        tile.bitmap = null;
-        synchronized (sFreeTileLock) {
-            tile.nextFreeTile = sFreeTileHead;
-            sFreeTileHead = tile;
-        }
-    }
-
-    private static Tile obtainTile() {
-        synchronized (sFreeTileLock) {
-            Tile result = sFreeTileHead;
-            if (result == null) return new Tile();
-            sFreeTileHead = result.nextFreeTile;
-            result.nextFreeTile = null;
-            return result;
-        }
-    }
-
-    private boolean uploadNextTile(GLCanvas canvas) {
-        if (mUploadIndex == mTiles.length) return true;
-
-        synchronized (mTiles) {
-            Tile next = mTiles[mUploadIndex++];
-
-            // Make sure tile has not already been recycled by the time
-            // this is called (race condition in onGLIdle)
-            if (next.bitmap != null) {
-                boolean hasBeenLoad = next.isLoaded();
-                next.updateContent(canvas);
-
-                // It will take some time for a texture to be drawn for the first
-                // time. When scrolling, we need to draw several tiles on the screen
-                // at the same time. It may cause a UI jank even these textures has
-                // been uploaded.
-                if (!hasBeenLoad) next.draw(canvas, 0, 0);
-            }
-        }
-        return mUploadIndex == mTiles.length;
-    }
-
-    public TiledTexture(Bitmap bitmap) {
-        mWidth = bitmap.getWidth();
-        mHeight = bitmap.getHeight();
-        ArrayList<Tile> list = new ArrayList<Tile>();
-
-        for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) {
-            for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) {
-                Tile tile = obtainTile();
-                tile.offsetX = x;
-                tile.offsetY = y;
-                tile.bitmap = bitmap;
-                tile.setSize(
-                        Math.min(CONTENT_SIZE, mWidth - x),
-                        Math.min(CONTENT_SIZE, mHeight - y));
-                list.add(tile);
-            }
-        }
-        mTiles = list.toArray(new Tile[list.size()]);
-    }
-
-    public boolean isReady() {
-        return mUploadIndex == mTiles.length;
-    }
-
-    // Can be called in UI thread.
-    public void recycle() {
-        synchronized (mTiles) {
-            for (int i = 0, n = mTiles.length; i < n; ++i) {
-                freeTile(mTiles[i]);
-            }
-        }
-    }
-
-    public static void freeResources() {
-        sUploadBitmap = null;
-        sCanvas = null;
-        sBitmapPaint = null;
-        sPaint = null;
-    }
-
-    public static void prepareResources() {
-        sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888);
-        sCanvas = new Canvas(sUploadBitmap);
-        sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-        sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
-        sPaint = new Paint();
-        sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
-        sPaint.setColor(Color.TRANSPARENT);
-    }
-
-    // We want to draw the "source" on the "target".
-    // This method is to find the "output" rectangle which is
-    // the corresponding area of the "src".
-    //                                   (x,y)  target
-    // (x0,y0)  source                     +---------------+
-    //    +----------+                     |               |
-    //    | src      |                     | output        |
-    //    | +--+     |    linear map       | +----+        |
-    //    | +--+     |    ---------->      | |    |        |
-    //    |          | by (scaleX, scaleY) | +----+        |
-    //    +----------+                     |               |
-    //      Texture                        +---------------+
-    //                                          Canvas
-    private static void mapRect(RectF output,
-            RectF src, float x0, float y0, float x, float y, float scaleX,
-            float scaleY) {
-        output.set(x + (src.left - x0) * scaleX,
-                y + (src.top - y0) * scaleY,
-                x + (src.right - x0) * scaleX,
-                y + (src.bottom - y0) * scaleY);
-    }
-
-    // Draws a mixed color of this texture and a specified color onto the
-    // a rectangle. The used color is: from * (1 - ratio) + to * ratio.
-    public void drawMixed(GLCanvas canvas, int color, float ratio,
-            int x, int y, int width, int height) {
-        RectF src = mSrcRect;
-        RectF dest = mDestRect;
-        float scaleX = (float) width / mWidth;
-        float scaleY = (float) height / mHeight;
-        synchronized (mTiles) {
-            for (int i = 0, n = mTiles.length; i < n; ++i) {
-                Tile t = mTiles[i];
-                src.set(0, 0, t.contentWidth, t.contentHeight);
-                src.offset(t.offsetX, t.offsetY);
-                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
-                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-                canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect);
-            }
-        }
-    }
-
-    // Draws the texture on to the specified rectangle.
-    @Override
-    public void draw(GLCanvas canvas, int x, int y, int width, int height) {
-        RectF src = mSrcRect;
-        RectF dest = mDestRect;
-        float scaleX = (float) width / mWidth;
-        float scaleY = (float) height / mHeight;
-        synchronized (mTiles) {
-            for (int i = 0, n = mTiles.length; i < n; ++i) {
-                Tile t = mTiles[i];
-                src.set(0, 0, t.contentWidth, t.contentHeight);
-                src.offset(t.offsetX, t.offsetY);
-                mapRect(dest, src, 0, 0, x, y, scaleX, scaleY);
-                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-                canvas.drawTexture(t, mSrcRect, mDestRect);
-            }
-        }
-    }
-
-    // Draws a sub region of this texture on to the specified rectangle.
-    public void draw(GLCanvas canvas, RectF source, RectF target) {
-        RectF src = mSrcRect;
-        RectF dest = mDestRect;
-        float x0 = source.left;
-        float y0 = source.top;
-        float x = target.left;
-        float y = target.top;
-        float scaleX = target.width() / source.width();
-        float scaleY = target.height() / source.height();
-
-        synchronized (mTiles) {
-            for (int i = 0, n = mTiles.length; i < n; ++i) {
-                Tile t = mTiles[i];
-                src.set(0, 0, t.contentWidth, t.contentHeight);
-                src.offset(t.offsetX, t.offsetY);
-                if (!src.intersect(source)) continue;
-                mapRect(dest, src, x0, y0, x, y, scaleX, scaleY);
-                src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY);
-                canvas.drawTexture(t, src, dest);
-            }
-        }
-    }
-
-    @Override
-    public int getWidth() {
-        return mWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        return mHeight;
-    }
-
-    @Override
-    public void draw(GLCanvas canvas, int x, int y) {
-        draw(canvas, x, y, mWidth, mHeight);
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return false;
-    }
-}
diff --git a/src/com/android/gallery3d/ui/UndoBarView.java b/src/com/android/gallery3d/ui/UndoBarView.java
index 8c9836d..42f12ae 100644
--- a/src/com/android/gallery3d/ui/UndoBarView.java
+++ b/src/com/android/gallery3d/ui/UndoBarView.java
@@ -21,6 +21,10 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.NinePatchTexture;
+import com.android.gallery3d.glrenderer.ResourceTexture;
+import com.android.gallery3d.glrenderer.StringTexture;
 import com.android.gallery3d.util.GalleryUtils;
 
 public class UndoBarView extends GLView {
diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java
deleted file mode 100644
index bb86d05..0000000
--- a/src/com/android/gallery3d/ui/UploadedTexture.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.opengl.GLUtils;
-
-import com.android.gallery3d.common.Utils;
-
-import java.util.HashMap;
-
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11Ext;
-
-// UploadedTextures use a Bitmap for the content of the texture.
-//
-// Subclasses should implement onGetBitmap() to provide the Bitmap and
-// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
-// is not needed anymore.
-//
-// isContentValid() is meaningful only when the isLoaded() returns true.
-// It means whether the content needs to be updated.
-//
-// The user of this class should call recycle() when the texture is not
-// needed anymore.
-//
-// By default an UploadedTexture is opaque (so it can be drawn faster without
-// blending). The user or subclass can override it using setOpaque().
-abstract class UploadedTexture extends BasicTexture {
-
-    // To prevent keeping allocation the borders, we store those used borders here.
-    // Since the length will be power of two, it won't use too much memory.
-    private static HashMap<BorderKey, Bitmap> sBorderLines =
-            new HashMap<BorderKey, Bitmap>();
-    private static BorderKey sBorderKey = new BorderKey();
-
-    @SuppressWarnings("unused")
-    private static final String TAG = "Texture";
-    private boolean mContentValid = true;
-
-    // indicate this textures is being uploaded in background
-    private boolean mIsUploading = false;
-    private boolean mOpaque = true;
-    private boolean mThrottled = false;
-    private static int sUploadedCount;
-    private static final int UPLOAD_LIMIT = 100;
-
-    protected Bitmap mBitmap;
-    private int mBorder;
-
-    protected UploadedTexture() {
-        this(false);
-    }
-
-    protected UploadedTexture(boolean hasBorder) {
-        super(null, 0, STATE_UNLOADED);
-        if (hasBorder) {
-            setBorder(true);
-            mBorder = 1;
-        }
-    }
-
-    protected void setIsUploading(boolean uploading) {
-        mIsUploading = uploading;
-    }
-
-    public boolean isUploading() {
-        return mIsUploading;
-    }
-
-    private static class BorderKey implements Cloneable {
-        public boolean vertical;
-        public Config config;
-        public int length;
-
-        @Override
-        public int hashCode() {
-            int x = config.hashCode() ^ length;
-            return vertical ? x : -x;
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (!(object instanceof BorderKey)) return false;
-            BorderKey o = (BorderKey) object;
-            return vertical == o.vertical
-                    && config == o.config && length == o.length;
-        }
-
-        @Override
-        public BorderKey clone() {
-            try {
-                return (BorderKey) super.clone();
-            } catch (CloneNotSupportedException e) {
-                throw new AssertionError(e);
-            }
-        }
-    }
-
-    protected void setThrottled(boolean throttled) {
-        mThrottled = throttled;
-    }
-
-    private static Bitmap getBorderLine(
-            boolean vertical, Config config, int length) {
-        BorderKey key = sBorderKey;
-        key.vertical = vertical;
-        key.config = config;
-        key.length = length;
-        Bitmap bitmap = sBorderLines.get(key);
-        if (bitmap == null) {
-            bitmap = vertical
-                    ? Bitmap.createBitmap(1, length, config)
-                    : Bitmap.createBitmap(length, 1, config);
-            sBorderLines.put(key.clone(), bitmap);
-        }
-        return bitmap;
-    }
-
-    private Bitmap getBitmap() {
-        if (mBitmap == null) {
-            mBitmap = onGetBitmap();
-            int w = mBitmap.getWidth() + mBorder * 2;
-            int h = mBitmap.getHeight() + mBorder * 2;
-            if (mWidth == UNSPECIFIED) {
-                setSize(w, h);
-            }
-        }
-        return mBitmap;
-    }
-
-    private void freeBitmap() {
-        Utils.assertTrue(mBitmap != null);
-        onFreeBitmap(mBitmap);
-        mBitmap = null;
-    }
-
-    @Override
-    public int getWidth() {
-        if (mWidth == UNSPECIFIED) getBitmap();
-        return mWidth;
-    }
-
-    @Override
-    public int getHeight() {
-        if (mWidth == UNSPECIFIED) getBitmap();
-        return mHeight;
-    }
-
-    protected abstract Bitmap onGetBitmap();
-
-    protected abstract void onFreeBitmap(Bitmap bitmap);
-
-    protected void invalidateContent() {
-        if (mBitmap != null) freeBitmap();
-        mContentValid = false;
-        mWidth = UNSPECIFIED;
-        mHeight = UNSPECIFIED;
-    }
-
-    /**
-     * Whether the content on GPU is valid.
-     */
-    public boolean isContentValid() {
-        return isLoaded() && mContentValid;
-    }
-
-    /**
-     * Updates the content on GPU's memory.
-     * @param canvas
-     */
-    public void updateContent(GLCanvas canvas) {
-        if (!isLoaded()) {
-            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
-                return;
-            }
-            uploadToCanvas(canvas);
-        } else if (!mContentValid) {
-            Bitmap bitmap = getBitmap();
-            int format = GLUtils.getInternalFormat(bitmap);
-            int type = GLUtils.getType(bitmap);
-            canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId);
-            GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder,
-                    bitmap, format, type);
-            freeBitmap();
-            mContentValid = true;
-        }
-    }
-
-    public static void resetUploadLimit() {
-        sUploadedCount = 0;
-    }
-
-    public static boolean uploadLimitReached() {
-        return sUploadedCount > UPLOAD_LIMIT;
-    }
-
-    static int[] sTextureId = new int[1];
-    static float[] sCropRect = new float[4];
-
-    private void uploadToCanvas(GLCanvas canvas) {
-        GL11 gl = canvas.getGLInstance();
-
-        Bitmap bitmap = getBitmap();
-        if (bitmap != null) {
-            try {
-                int bWidth = bitmap.getWidth();
-                int bHeight = bitmap.getHeight();
-                int width = bWidth + mBorder * 2;
-                int height = bHeight + mBorder * 2;
-                int texWidth = getTextureWidth();
-                int texHeight = getTextureHeight();
-
-                Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight);
-
-                // Define a vertically flipped crop rectangle for
-                // OES_draw_texture.
-                // The four values in sCropRect are: left, bottom, width, and
-                // height. Negative value of width or height means flip.
-                sCropRect[0] = mBorder;
-                sCropRect[1] = mBorder + bHeight;
-                sCropRect[2] = bWidth;
-                sCropRect[3] = -bHeight;
-
-                // Upload the bitmap to a new texture.
-                GLId.glGenTextures(1, sTextureId, 0);
-                gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
-                gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
-                        GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
-                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
-                        GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
-                gl.glTexParameteri(GL11.GL_TEXTURE_2D,
-                        GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
-                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
-                        GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
-                gl.glTexParameterf(GL11.GL_TEXTURE_2D,
-                        GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
-
-                if (bWidth == texWidth && bHeight == texHeight) {
-                    GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
-                } else {
-                    int format = GLUtils.getInternalFormat(bitmap);
-                    int type = GLUtils.getType(bitmap);
-                    Config config = bitmap.getConfig();
-
-                    gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
-                            texWidth, texHeight, 0, format, type, null);
-                    GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
-                            mBorder, mBorder, bitmap, format, type);
-
-                    if (mBorder > 0) {
-                        // Left border
-                        Bitmap line = getBorderLine(true, config, texHeight);
-                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
-                                0, 0, line, format, type);
-
-                        // Top border
-                        line = getBorderLine(false, config, texWidth);
-                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
-                                0, 0, line, format, type);
-                    }
-
-                    // Right border
-                    if (mBorder + bWidth < texWidth) {
-                        Bitmap line = getBorderLine(true, config, texHeight);
-                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
-                                mBorder + bWidth, 0, line, format, type);
-                    }
-
-                    // Bottom border
-                    if (mBorder + bHeight < texHeight) {
-                        Bitmap line = getBorderLine(false, config, texWidth);
-                        GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
-                                0, mBorder + bHeight, line, format, type);
-                    }
-                }
-            } finally {
-                freeBitmap();
-            }
-            // Update texture state.
-            setAssociatedCanvas(canvas);
-            mId = sTextureId[0];
-            mState = STATE_LOADED;
-            mContentValid = true;
-        } else {
-            mState = STATE_ERROR;
-            throw new RuntimeException("Texture load fail, no bitmap");
-        }
-    }
-
-    @Override
-    protected boolean onBind(GLCanvas canvas) {
-        updateContent(canvas);
-        return isContentValid();
-    }
-
-    @Override
-    protected int getTarget() {
-        return GL11.GL_TEXTURE_2D;
-    }
-
-    public void setOpaque(boolean isOpaque) {
-        mOpaque = isOpaque;
-    }
-
-    @Override
-    public boolean isOpaque() {
-        return mOpaque;
-    }
-
-    @Override
-    public void recycle() {
-        super.recycle();
-        if (mBitmap != null) freeBitmap();
-    }
-}
diff --git a/src/com/android/gallery3d/util/AccessibilityUtils.java b/src/com/android/gallery3d/util/AccessibilityUtils.java
new file mode 100644
index 0000000..9df8e4e
--- /dev/null
+++ b/src/com/android/gallery3d/util/AccessibilityUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.util;
+
+import android.content.Context;
+import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.gallery3d.common.ApiHelper;
+
+/**
+ * AccessibilityUtils provides functions needed in accessibility mode. All the functions
+ * in this class are made compatible with gingerbread and later API's
+*/
+public class AccessibilityUtils {
+    public static void makeAnnouncement(View view, CharSequence announcement) {
+        if (view == null)
+            return;
+        if (ApiHelper.HAS_ANNOUNCE_FOR_ACCESSIBILITY) {
+            view.announceForAccessibility(announcement);
+        } else {
+            // For API 15 and earlier, we need to construct an accessibility event
+            Context ctx = view.getContext();
+            AccessibilityManager am = (AccessibilityManager) ctx.getSystemService(
+                    Context.ACCESSIBILITY_SERVICE);
+            if (!am.isEnabled()) return;
+            AccessibilityEvent event = AccessibilityEvent.obtain(
+                    AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+            AccessibilityRecordCompat arc = new AccessibilityRecordCompat(event);
+            arc.setSource(view);
+            event.setClassName(view.getClass().getName());
+            event.setPackageName(view.getContext().getPackageName());
+            event.setEnabled(view.isEnabled());
+            event.getText().add(announcement);
+            am.sendAccessibilityEvent(event);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/gallery3d/util/BucketNames.java b/src/com/android/gallery3d/util/BucketNames.java
index df7684a..990dc82 100644
--- a/src/com/android/gallery3d/util/BucketNames.java
+++ b/src/com/android/gallery3d/util/BucketNames.java
@@ -21,7 +21,9 @@
  */
 public class BucketNames {
 
+    public static final String CAMERA = "DCIM/Camera";
     public static final String IMPORTED = "Imported";
     public static final String DOWNLOAD = "download";
     public static final String EDITED_ONLINE_PHOTOS = "EditedOnlinePhotos";
+    public static final String SCREENSHOTS = "Pictures/Screenshots";
 }
diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java
index 547e2dd..9245e2c 100644
--- a/src/com/android/gallery3d/util/GalleryUtils.java
+++ b/src/com/android/gallery3d/util/GalleryUtils.java
@@ -46,6 +46,7 @@
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
+import java.io.File;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
@@ -94,14 +95,6 @@
         TiledScreenNail.setMaxSide(maxPixels / 2);
     }
 
-    public static boolean isHighResolution(Context context) {
-        DisplayMetrics metrics = new DisplayMetrics();
-        WindowManager wm = (WindowManager)
-                context.getSystemService(Context.WINDOW_SERVICE);
-        wm.getDefaultDisplay().getMetrics(metrics);
-        return metrics.heightPixels > 2048 ||  metrics.widthPixels > 2048;
-    }
-
     public static float[] intColorToFloatARGBArray(int from) {
         return new float[] {
             Color.alpha(from) / 255f,
@@ -259,7 +252,9 @@
     }
 
     public static void startGalleryActivity(Context context) {
-        Intent intent = new Intent(context, Gallery.class);
+        Intent intent = new Intent(context, Gallery.class)
+                .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+                | Intent.FLAG_ACTIVITY_NEW_TASK);
         context.startActivity(intent);
     }
 
@@ -315,6 +310,26 @@
         return path.toLowerCase().hashCode();
     }
 
+    // Return the local path that matches the given bucketId. If no match is
+    // found, return null
+    public static String searchDirForPath(File dir, int bucketId) {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    String path = file.getAbsolutePath();
+                    if (GalleryUtils.getBucketId(path) == bucketId) {
+                        return path;
+                    } else {
+                        path = searchDirForPath(file, bucketId);
+                        if (path != null) return path;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
     // Returns a (localized) string for the given duration (in seconds).
     public static String formatDuration(final Context context, int duration) {
         int h = duration / 3600;
diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java
index 082089a..2c4dc2c 100644
--- a/src/com/android/gallery3d/util/IntArray.java
+++ b/src/com/android/gallery3d/util/IntArray.java
@@ -31,6 +31,11 @@
         mData[mSize++] = value;
     }
 
+    public int removeLast() {
+        mSize--;
+        return mData[mSize];
+    }
+
     public int size() {
         return mSize;
     }
diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java
index 83b6b32..0438005 100644
--- a/src/com/android/gallery3d/util/MediaSetUtils.java
+++ b/src/com/android/gallery3d/util/MediaSetUtils.java
@@ -29,7 +29,8 @@
     public static final Comparator<MediaSet> NAME_COMPARATOR = new NameComparator();
 
     public static final int CAMERA_BUCKET_ID = GalleryUtils.getBucketId(
-            Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera");
+            Environment.getExternalStorageDirectory().toString() + "/"
+            + BucketNames.CAMERA);
     public static final int DOWNLOAD_BUCKET_ID = GalleryUtils.getBucketId(
             Environment.getExternalStorageDirectory().toString() + "/"
             + BucketNames.DOWNLOAD);
@@ -41,7 +42,7 @@
             + BucketNames.IMPORTED);
     public static final int SNAPSHOT_BUCKET_ID = GalleryUtils.getBucketId(
             Environment.getExternalStorageDirectory().toString() +
-            "/Pictures/Screenshots");
+            "/" + BucketNames.SCREENSHOTS);
 
     private static final Path[] CAMERA_PATHS = {
             Path.fromString("/local/all/" + CAMERA_BUCKET_ID),
diff --git a/src/com/android/gallery3d/util/SaveVideoFileInfo.java b/src/com/android/gallery3d/util/SaveVideoFileInfo.java
new file mode 100644
index 0000000..c7e5e85
--- /dev/null
+++ b/src/com/android/gallery3d/util/SaveVideoFileInfo.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.util;
+
+import java.io.File;
+
+public class SaveVideoFileInfo {
+    public File mFile = null;
+    public String mFileName = null;
+    // This the full directory path.
+    public File mDirectory = null;
+    // This is just the folder's name.
+    public String mFolderName = null;
+
+}
diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
new file mode 100644
index 0000000..9e8f73a
--- /dev/null
+++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.util;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class SaveVideoFileUtils {
+    // Copy from SaveCopyTask.java in terms of how to handle the destination
+    // path and filename : querySource() and getSaveDirectory().
+    public interface ContentResolverQueryCallback {
+        void onCursorResult(Cursor cursor);
+    }
+
+    // This function can decide which folder to save the video file, and generate
+    // the needed information for the video file including filename.
+    public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat,
+            ContentResolver contentResolver, Uri uri, String defaultFolderName) {
+        SaveVideoFileInfo dstFileInfo = new SaveVideoFileInfo();
+        // Use the default save directory if the source directory cannot be
+        // saved.
+        dstFileInfo.mDirectory = getSaveDirectory(contentResolver, uri);
+        if ((dstFileInfo.mDirectory == null) || !dstFileInfo.mDirectory.canWrite()) {
+            dstFileInfo.mDirectory = new File(Environment.getExternalStorageDirectory(),
+                    BucketNames.DOWNLOAD);
+            dstFileInfo.mFolderName = defaultFolderName;
+        } else {
+            dstFileInfo.mFolderName = dstFileInfo.mDirectory.getName();
+        }
+        dstFileInfo.mFileName = new SimpleDateFormat(fileNameFormat).format(new Date());
+
+        dstFileInfo.mFile = new File(dstFileInfo.mDirectory, dstFileInfo.mFileName + ".mp4");
+        return dstFileInfo;
+    }
+
+    private static void querySource(ContentResolver contentResolver, Uri uri,
+            String[] projection, ContentResolverQueryCallback callback) {
+        Cursor cursor = null;
+        try {
+            cursor = contentResolver.query(uri, projection, null, null, null);
+            if ((cursor != null) && cursor.moveToNext()) {
+                callback.onCursorResult(cursor);
+            }
+        } catch (Exception e) {
+            // Ignore error for lacking the data column from the source.
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private static File getSaveDirectory(ContentResolver contentResolver, Uri uri) {
+        final File[] dir = new File[1];
+        querySource(contentResolver, uri,
+                new String[] { VideoColumns.DATA },
+                new ContentResolverQueryCallback() {
+            @Override
+            public void onCursorResult(Cursor cursor) {
+                dir[0] = new File(cursor.getString(0)).getParentFile();
+            }
+        });
+        return dir[0];
+    }
+
+
+    /**
+     * Insert the content (saved file) with proper video properties.
+     */
+    public static Uri insertContent(SaveVideoFileInfo mDstFileInfo,
+            ContentResolver contentResolver, Uri uri ) {
+        long nowInMs = System.currentTimeMillis();
+        long nowInSec = nowInMs / 1000;
+        final ContentValues values = new ContentValues(12);
+        values.put(Video.Media.TITLE, mDstFileInfo.mFileName);
+        values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName());
+        values.put(Video.Media.MIME_TYPE, "video/mp4");
+        values.put(Video.Media.DATE_TAKEN, nowInMs);
+        values.put(Video.Media.DATE_MODIFIED, nowInSec);
+        values.put(Video.Media.DATE_ADDED, nowInSec);
+        values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath());
+        values.put(Video.Media.SIZE, mDstFileInfo.mFile.length());
+        // Copy the data taken and location info from src.
+        String[] projection = new String[] {
+                VideoColumns.DATE_TAKEN,
+                VideoColumns.LATITUDE,
+                VideoColumns.LONGITUDE,
+                VideoColumns.RESOLUTION,
+        };
+
+        // Copy some info from the source file.
+        querySource(contentResolver, uri, projection,
+                new ContentResolverQueryCallback() {
+                @Override
+                    public void onCursorResult(Cursor cursor) {
+                        long timeTaken = cursor.getLong(0);
+                        if (timeTaken > 0) {
+                            values.put(Video.Media.DATE_TAKEN, timeTaken);
+                        }
+                        double latitude = cursor.getDouble(1);
+                        double longitude = cursor.getDouble(2);
+                        // TODO: Change || to && after the default location
+                        // issue is
+                        // fixed.
+                        if ((latitude != 0f) || (longitude != 0f)) {
+                            values.put(Video.Media.LATITUDE, latitude);
+                            values.put(Video.Media.LONGITUDE, longitude);
+                        }
+                        values.put(Video.Media.RESOLUTION, cursor.getString(3));
+
+                    }
+                });
+
+        return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values);
+    }
+
+}
diff --git a/src/com/android/photos/AlbumActivity.java b/src/com/android/photos/AlbumActivity.java
new file mode 100644
index 0000000..c616b99
--- /dev/null
+++ b/src/com/android/photos/AlbumActivity.java
@@ -0,0 +1,48 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class AlbumActivity extends Activity implements MultiChoiceManager.Provider {
+
+    public static final String KEY_ALBUM_URI = AlbumFragment.KEY_ALBUM_URI;
+    public static final String KEY_ALBUM_TITLE = AlbumFragment.KEY_ALBUM_TITLE;
+
+    private MultiChoiceManager mMultiChoiceManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle intentExtras = getIntent().getExtras();
+        mMultiChoiceManager = new MultiChoiceManager(this);
+        if (savedInstanceState == null) {
+            AlbumFragment albumFragment = new AlbumFragment();
+            mMultiChoiceManager.setDelegate(albumFragment);
+            albumFragment.setArguments(intentExtras);
+            getFragmentManager().beginTransaction().add(android.R.id.content,
+                    albumFragment).commit();
+        }
+        getActionBar().setTitle(intentExtras.getString(KEY_ALBUM_TITLE));
+    }
+
+    @Override
+    public MultiChoiceManager getMultiChoiceManager() {
+        return mMultiChoiceManager;
+    }
+}
diff --git a/src/com/android/photos/AlbumFragment.java b/src/com/android/photos/AlbumFragment.java
new file mode 100644
index 0000000..406fd2a
--- /dev/null
+++ b/src/com/android/photos/AlbumFragment.java
@@ -0,0 +1,168 @@
+/*
+ * 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.photos;
+
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.Gallery;
+import com.android.photos.adapters.PhotoThumbnailAdapter;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.shims.MediaItemsLoader;
+import com.android.photos.views.HeaderGridView;
+
+import java.util.ArrayList;
+
+public class AlbumFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> {
+
+    protected static final String KEY_ALBUM_URI = "AlbumUri";
+    protected static final String KEY_ALBUM_TITLE = "AlbumTitle";
+    private static final int LOADER_ALBUM = 1;
+
+    private LoaderCompatShim<Cursor> mLoaderCompatShim;
+    private PhotoThumbnailAdapter mAdapter;
+    private String mAlbumPath;
+    private String mAlbumTitle;
+    private View mHeaderView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Context context = getActivity();
+        mAdapter = new PhotoThumbnailAdapter(context);
+        Bundle args = getArguments();
+        if (args != null) {
+            mAlbumPath = args.getString(KEY_ALBUM_URI, null);
+            mAlbumTitle = args.getString(KEY_ALBUM_TITLE, null);
+        }
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        getLoaderManager().initLoader(LOADER_ALBUM, null, this);
+        return inflater.inflate(R.layout.album_content, container, false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        // TODO: Remove once UI stabilizes
+        getGridView().setColumnWidth(MediaItemsLoader.getThumbnailSize());
+    }
+
+    private void updateHeaderView() {
+        if (mHeaderView == null) {
+            mHeaderView = LayoutInflater.from(getActivity())
+                    .inflate(R.layout.album_header, getGridView(), false);
+            ((HeaderGridView) getGridView()).addHeaderView(mHeaderView, null, false);
+
+            // TODO remove this when the data model stabilizes
+            mHeaderView.setMinimumHeight(200);
+        }
+        ImageView iv = (ImageView) mHeaderView.findViewById(R.id.album_header_image);
+        TextView title = (TextView) mHeaderView.findViewById(R.id.album_header_title);
+        TextView subtitle = (TextView) mHeaderView.findViewById(R.id.album_header_subtitle);
+        title.setText(mAlbumTitle);
+        int count = mAdapter.getCount();
+        subtitle.setText(getActivity().getResources().getQuantityString(
+                R.plurals.number_of_photos, count, count));
+        if (count > 0) {
+            iv.setImageDrawable(mLoaderCompatShim.drawableForItem(mAdapter.getItem(0), null));
+        }
+    }
+
+    @Override
+    public void onGridItemClick(GridView g, View v, int position, long id) {
+        if (mLoaderCompatShim == null) {
+            // Not fully initialized yet, discard
+            return;
+        }
+        Cursor item = (Cursor) getItemAtPosition(position);
+        Uri uri = mLoaderCompatShim.uriForItem(item);
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.setClass(getActivity(), Gallery.class);
+        startActivity(intent);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // TODO: Switch to PhotoSetLoader
+        MediaItemsLoader loader = new MediaItemsLoader(getActivity(), mAlbumPath);
+        mLoaderCompatShim = loader;
+        mAdapter.setDrawableFactory(mLoaderCompatShim);
+        return loader;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader,
+            Cursor data) {
+        mAdapter.swapCursor(data);
+        updateHeaderView();
+        setAdapter(mAdapter);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Override
+    public int getItemMediaType(Object item) {
+        return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE);
+    }
+
+    @Override
+    public int getItemSupportedOperations(Object item) {
+        return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS);
+    }
+
+    private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1);
+    @Override
+    public ArrayList<Uri> getSubItemUrisForItem(Object item) {
+        mSubItemUriTemp.clear();
+        mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item));
+        return mSubItemUriTemp;
+    }
+
+    @Override
+    public void deleteItemWithPath(Object itemPath) {
+        mLoaderCompatShim.deleteItemWithPath(itemPath);
+    }
+
+    @Override
+    public Uri getItemUri(Object item) {
+        return mLoaderCompatShim.uriForItem((Cursor) item);
+    }
+
+    @Override
+    public Object getPathForItem(Object item) {
+        return mLoaderCompatShim.getPathForItem((Cursor) item);
+    }
+}
diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java
new file mode 100644
index 0000000..bc5289e
--- /dev/null
+++ b/src/com/android/photos/AlbumSetFragment.java
@@ -0,0 +1,135 @@
+/*
+ * 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.photos;
+
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore.Files.FileColumns;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+
+import com.android.gallery3d.R;
+import com.android.photos.adapters.AlbumSetCursorAdapter;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.shims.MediaSetLoader;
+
+import java.util.ArrayList;
+
+
+public class AlbumSetFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> {
+
+    private AlbumSetCursorAdapter mAdapter;
+    private LoaderCompatShim<Cursor> mLoaderCompatShim;
+
+    private static final int LOADER_ALBUMSET = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Context context = getActivity();
+        mAdapter = new AlbumSetCursorAdapter(context);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = super.onCreateView(inflater, container, savedInstanceState);
+        getLoaderManager().initLoader(LOADER_ALBUMSET, null, this);
+        return root;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        getGridView().setColumnWidth(getActivity().getResources()
+                .getDimensionPixelSize(R.dimen.album_set_item_width));
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // TODO: Switch to AlbumSetLoader
+        MediaSetLoader loader = new MediaSetLoader(getActivity());
+        mAdapter.setDrawableFactory(loader);
+        mLoaderCompatShim = loader;
+        return loader;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader,
+            Cursor data) {
+        mAdapter.swapCursor(data);
+        setAdapter(mAdapter);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Override
+    public void onGridItemClick(GridView g, View v, int position, long id) {
+        if (mLoaderCompatShim == null) {
+            // Not fully initialized yet, discard
+            return;
+        }
+        Cursor item = (Cursor) getItemAtPosition(position);
+        Context context = getActivity();
+        Intent intent = new Intent(context, AlbumActivity.class);
+        intent.putExtra(AlbumActivity.KEY_ALBUM_URI,
+                mLoaderCompatShim.getPathForItem(item).toString());
+        intent.putExtra(AlbumActivity.KEY_ALBUM_TITLE,
+                item.getString(AlbumSetLoader.INDEX_TITLE));
+        context.startActivity(intent);
+    }
+
+    @Override
+    public int getItemMediaType(Object item) {
+        return FileColumns.MEDIA_TYPE_NONE;
+    }
+
+    @Override
+    public int getItemSupportedOperations(Object item) {
+        return ((Cursor) item).getInt(AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS);
+    }
+
+    @Override
+    public ArrayList<Uri> getSubItemUrisForItem(Object item) {
+        return mLoaderCompatShim.urisForSubItems((Cursor) item);
+    }
+
+    @Override
+    public void deleteItemWithPath(Object itemPath) {
+        mLoaderCompatShim.deleteItemWithPath(itemPath);
+    }
+
+    @Override
+    public Uri getItemUri(Object item) {
+        return mLoaderCompatShim.uriForItem((Cursor) item);
+    }
+
+    @Override
+    public Object getPathForItem(Object item) {
+        return mLoaderCompatShim.getPathForItem((Cursor) item);
+    }
+}
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 0000000..1c71151
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,87 @@
+/*
+ * 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.photos;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
+import android.util.Log;
+import com.android.photos.views.TiledImageRenderer;
+
+import java.io.IOException;
+
+public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
+
+    BitmapRegionDecoder mDecoder;
+
+
+    public BitmapRegionTileSource(String path) {
+        try {
+            mDecoder = BitmapRegionDecoder.newInstance(path, true);
+        } catch (IOException e) {
+            Log.w("BitmapRegionTileSource", "ctor failed", e);
+        }
+    }
+
+    @Override
+    public int getTileSize() {
+        return 256;
+    }
+
+    @Override
+    public int getImageWidth() {
+        return mDecoder.getWidth();
+    }
+
+    @Override
+    public int getImageHeight() {
+        return mDecoder.getHeight();
+    }
+
+    @Override
+    public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+        int tileSize = getTileSize();
+        int t = tileSize << level;
+
+        Rect wantRegion = new Rect(x, y, x + t, y + t);
+
+        if (bitmap == null) {
+            bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+        }
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        options.inPreferQualityOverSpeed = true;
+        options.inSampleSize =  (1 << level);
+        options.inBitmap = bitmap;
+
+        try {
+            // In CropImage, we may call the decodeRegion() concurrently.
+            bitmap = mDecoder.decodeRegion(wantRegion, options);
+        } finally {
+            if (options.inBitmap != bitmap && options.inBitmap != null) {
+                options.inBitmap = null;
+            }
+        }
+
+        if (bitmap == null) {
+            Log.w("BitmapRegionTileSource", "fail in decoding region");
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/photos/FullscreenViewer.java b/src/com/android/photos/FullscreenViewer.java
new file mode 100644
index 0000000..50ea1ba
--- /dev/null
+++ b/src/com/android/photos/FullscreenViewer.java
@@ -0,0 +1,44 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.photos.views.TiledImageView;
+
+
+public class FullscreenViewer extends Activity {
+
+    private TiledImageView mTextureView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        String path = getIntent().getData().toString();
+        mTextureView = new TiledImageView(this);
+        mTextureView.setTileSource(new BitmapRegionTileSource(path));
+        setContentView(mTextureView);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mTextureView.destroy();
+    }
+
+}
diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java
new file mode 100644
index 0000000..710767d
--- /dev/null
+++ b/src/com/android/photos/GalleryActivity.java
@@ -0,0 +1,184 @@
+/*
+ * 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.photos;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+
+public class GalleryActivity extends Activity implements MultiChoiceManager.Provider {
+
+    private MultiChoiceManager mMultiChoiceManager;
+    private ViewPager mViewPager;
+    private TabsAdapter mTabsAdapter;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mMultiChoiceManager = new MultiChoiceManager(this);
+        mViewPager = new ViewPager(this);
+        mViewPager.setId(R.id.viewpager);
+        setContentView(mViewPager);
+
+        ActionBar ab = getActionBar();
+        ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+        ab.setDisplayShowHomeEnabled(false);
+        ab.setDisplayShowTitleEnabled(false);
+
+        mTabsAdapter = new TabsAdapter(this, mViewPager);
+        mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_photos),
+                PhotoSetFragment.class, null);
+        mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_albums),
+                AlbumSetFragment.class, null);
+
+        if (savedInstanceState != null) {
+            ab.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("tab", getActionBar().getSelectedNavigationIndex());
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.gallery, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+        case R.id.menu_camera:
+            Intent intent = new Intent(this, CameraActivity.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+            return true;
+        default:
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    public static class TabsAdapter extends FragmentPagerAdapter implements
+            ActionBar.TabListener, ViewPager.OnPageChangeListener {
+
+        private final GalleryActivity mActivity;
+        private final ActionBar mActionBar;
+        private final ViewPager mViewPager;
+        private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
+
+        static final class TabInfo {
+
+            private final Class<?> clss;
+            private final Bundle args;
+
+            TabInfo(Class<?> _class, Bundle _args) {
+                clss = _class;
+                args = _args;
+            }
+        }
+
+        public TabsAdapter(GalleryActivity activity, ViewPager pager) {
+            super(activity.getFragmentManager());
+            mActivity = activity;
+            mActionBar = activity.getActionBar();
+            mViewPager = pager;
+            mViewPager.setAdapter(this);
+            mViewPager.setOnPageChangeListener(this);
+        }
+
+        public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) {
+            TabInfo info = new TabInfo(clss, args);
+            tab.setTag(info);
+            tab.setTabListener(this);
+            mTabs.add(info);
+            mActionBar.addTab(tab);
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getCount() {
+            return mTabs.size();
+        }
+
+        @Override
+        public Fragment getItem(int position) {
+            TabInfo info = mTabs.get(position);
+            return Fragment.instantiate(mActivity, info.clss.getName(),
+                    info.args);
+        }
+
+        @Override
+        public void onPageScrolled(int position, float positionOffset,
+                int positionOffsetPixels) {
+        }
+
+        @Override
+        public void onPageSelected(int position) {
+            mActionBar.setSelectedNavigationItem(position);
+        }
+
+        @Override
+        public void setPrimaryItem(ViewGroup container, int position, Object object) {
+            super.setPrimaryItem(container, position, object);
+            mActivity.mMultiChoiceManager.setDelegate((MultiChoiceManager.Delegate) object);
+        }
+
+        @Override
+        public void onPageScrollStateChanged(int state) {
+        }
+
+        @Override
+        public void onTabSelected(Tab tab, FragmentTransaction ft) {
+            Object tag = tab.getTag();
+            for (int i = 0; i < mTabs.size(); i++) {
+                if (mTabs.get(i) == tag) {
+                    mViewPager.setCurrentItem(i);
+                }
+            }
+        }
+
+        @Override
+        public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+        }
+
+        @Override
+        public void onTabReselected(Tab tab, FragmentTransaction ft) {
+        }
+    }
+
+    @Override
+    public MultiChoiceManager getMultiChoiceManager() {
+        return mMultiChoiceManager;
+    }
+}
diff --git a/src/com/android/photos/MultiChoiceManager.java b/src/com/android/photos/MultiChoiceManager.java
new file mode 100644
index 0000000..f315cf9
--- /dev/null
+++ b/src/com/android/photos/MultiChoiceManager.java
@@ -0,0 +1,294 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.provider.MediaStore.Files.FileColumns;
+import android.util.SparseBooleanArray;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.ShareActionProvider;
+import android.widget.ShareActionProvider.OnShareTargetSelectedListener;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.TrimVideo;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.util.GalleryUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MultiChoiceManager implements MultiChoiceModeListener,
+    OnShareTargetSelectedListener, SelectionManager.SelectedUriSource {
+
+    public interface Provider {
+        public MultiChoiceManager getMultiChoiceManager();
+    }
+
+    public interface Delegate {
+        public SparseBooleanArray getSelectedItemPositions();
+        public int getSelectedItemCount();
+        public int getItemMediaType(Object item);
+        public int getItemSupportedOperations(Object item);
+        public ArrayList<Uri> getSubItemUrisForItem(Object item);
+        public Uri getItemUri(Object item);
+        public Object getItemAtPosition(int position);
+        public Object getPathForItemAtPosition(int position);
+        public void deleteItemWithPath(Object itemPath);
+    }
+
+    private SelectionManager mSelectionManager;
+    private ShareActionProvider mShareActionProvider;
+    private ActionMode mActionMode;
+    private Context mContext;
+    private Delegate mDelegate;
+
+    private ArrayList<Uri> mSelectedShareableUrisArray = new ArrayList<Uri>();
+
+    public MultiChoiceManager(Activity activity) {
+        mContext = activity;
+        mSelectionManager = new SelectionManager(activity);
+    }
+
+    public void setDelegate(Delegate delegate) {
+        if (mDelegate == delegate) {
+            return;
+        }
+        if (mActionMode != null) {
+            mActionMode.finish();
+        }
+        mDelegate = delegate;
+    }
+
+    @Override
+    public ArrayList<Uri> getSelectedShareableUris() {
+        return mSelectedShareableUrisArray;
+    }
+
+    private void updateSelectedTitle(ActionMode mode) {
+        int count = mDelegate.getSelectedItemCount();
+        mode.setTitle(mContext.getResources().getQuantityString(
+                R.plurals.number_of_items_selected, count, count));
+    }
+
+    private String getItemMimetype(Object item) {
+        int type = mDelegate.getItemMediaType(item);
+        if (type == FileColumns.MEDIA_TYPE_IMAGE) {
+            return GalleryUtils.MIME_TYPE_IMAGE;
+        } else if (type == FileColumns.MEDIA_TYPE_VIDEO) {
+            return GalleryUtils.MIME_TYPE_VIDEO;
+        } else {
+            return GalleryUtils.MIME_TYPE_ALL;
+        }
+    }
+
+    @Override
+    public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
+            boolean checked) {
+        updateSelectedTitle(mode);
+        Object item = mDelegate.getItemAtPosition(position);
+
+        int supported = mDelegate.getItemSupportedOperations(item);
+
+        if ((supported & MediaObject.SUPPORT_SHARE) > 0) {
+            ArrayList<Uri> subItems = mDelegate.getSubItemUrisForItem(item);
+            if (checked) {
+                mSelectedShareableUrisArray.addAll(subItems);
+            } else {
+                mSelectedShareableUrisArray.removeAll(subItems);
+            }
+        }
+
+        mSelectionManager.onItemSelectedStateChanged(mShareActionProvider,
+                mDelegate.getItemMediaType(item),
+                supported,
+                checked);
+        updateActionItemVisibilities(mode.getMenu(),
+                mSelectionManager.getSupportedOperations());
+    }
+
+    private void updateActionItemVisibilities(Menu menu, int supportedOperations) {
+        MenuItem editItem = menu.findItem(R.id.menu_edit);
+        MenuItem deleteItem = menu.findItem(R.id.menu_delete);
+        MenuItem shareItem = menu.findItem(R.id.menu_share);
+        MenuItem cropItem = menu.findItem(R.id.menu_crop);
+        MenuItem trimItem = menu.findItem(R.id.menu_trim);
+        MenuItem muteItem = menu.findItem(R.id.menu_mute);
+        MenuItem setAsItem = menu.findItem(R.id.menu_set_as);
+
+        editItem.setVisible((supportedOperations & MediaObject.SUPPORT_EDIT) > 0);
+        deleteItem.setVisible((supportedOperations & MediaObject.SUPPORT_DELETE) > 0);
+        shareItem.setVisible((supportedOperations & MediaObject.SUPPORT_SHARE) > 0);
+        cropItem.setVisible((supportedOperations & MediaObject.SUPPORT_CROP) > 0);
+        trimItem.setVisible((supportedOperations & MediaObject.SUPPORT_TRIM) > 0);
+        muteItem.setVisible((supportedOperations & MediaObject.SUPPORT_MUTE) > 0);
+        setAsItem.setVisible((supportedOperations & MediaObject.SUPPORT_SETAS) > 0);
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        mSelectionManager.setSelectedUriSource(this);
+        mActionMode = mode;
+        MenuInflater inflater = mode.getMenuInflater();
+        inflater.inflate(R.menu.gallery_multiselect, menu);
+        MenuItem menuItem = menu.findItem(R.id.menu_share);
+        mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider();
+        mShareActionProvider.setOnShareTargetSelectedListener(this);
+        updateSelectedTitle(mode);
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        // onDestroyActionMode gets called when the share target was selected,
+        // but apparently before the ArrayList is serialized in the intent
+        // so we can't clear the old one here.
+        mSelectedShareableUrisArray = new ArrayList<Uri>();
+        mSelectionManager.onClearSelection();
+        mSelectionManager.setSelectedUriSource(null);
+        mShareActionProvider = null;
+        mActionMode = null;
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        updateSelectedTitle(mode);
+        return false;
+    }
+
+    @Override
+    public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) {
+        mActionMode.finish();
+        return false;
+    }
+
+    private static class BulkDeleteTask extends AsyncTask<Void, Void, Void> {
+        private Delegate mDelegate;
+        private List<Object> mPaths;
+
+        public BulkDeleteTask(Delegate delegate, List<Object> paths) {
+            mDelegate = delegate;
+            mPaths = paths;
+        }
+
+        @Override
+        protected Void doInBackground(Void... ignored) {
+            for (Object path : mPaths) {
+                mDelegate.deleteItemWithPath(path);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        int actionItemId = item.getItemId();
+        switch (actionItemId) {
+            case R.id.menu_delete:
+                BulkDeleteTask deleteTask = new BulkDeleteTask(mDelegate,
+                        getPathsForSelectedItems());
+                deleteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+                mode.finish();
+                return true;
+            case R.id.menu_edit:
+            case R.id.menu_crop:
+            case R.id.menu_trim:
+            case R.id.menu_mute:
+            case R.id.menu_set_as:
+                singleItemAction(getSelectedItem(), actionItemId);
+                mode.finish();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void singleItemAction(Object item, int actionItemId) {
+        Intent intent = new Intent();
+        String mime = getItemMimetype(item);
+        Uri uri = mDelegate.getItemUri(item);
+        switch (actionItemId) {
+            case R.id.menu_edit:
+                intent.setDataAndType(uri, mime)
+                      .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                      .setAction(Intent.ACTION_EDIT);
+                mContext.startActivity(Intent.createChooser(intent, null));
+                return;
+            case R.id.menu_crop:
+                intent.setDataAndType(uri, mime)
+                      .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                      .setAction(FilterShowActivity.CROP_ACTION)
+                      .setClass(mContext, FilterShowActivity.class);
+                mContext.startActivity(intent);
+                return;
+            case R.id.menu_trim:
+                intent.setData(uri)
+                      .setClass(mContext, TrimVideo.class);
+                mContext.startActivity(intent);
+                return;
+            case R.id.menu_mute:
+                /* TODO need a way to get the file path of an item
+                MuteVideo muteVideo = new MuteVideo(filePath,
+                        uri, (Activity) mContext);
+                muteVideo.muteInBackground();
+                */
+                return;
+            case R.id.menu_set_as:
+                intent.setDataAndType(uri, mime)
+                      .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                      .setAction(Intent.ACTION_ATTACH_DATA)
+                      .putExtra("mimeType", mime);
+                mContext.startActivity(Intent.createChooser(
+                        intent, mContext.getString(R.string.set_as)));
+                return;
+            default:
+                return;
+        }
+    }
+
+    private List<Object> getPathsForSelectedItems() {
+        List<Object> paths = new ArrayList<Object>();
+        SparseBooleanArray selected = mDelegate.getSelectedItemPositions();
+        for (int i = 0; i < selected.size(); i++) {
+            if (selected.valueAt(i)) {
+                paths.add(mDelegate.getPathForItemAtPosition(i));
+            }
+        }
+        return paths;
+    }
+
+    public Object getSelectedItem() {
+        if (mDelegate.getSelectedItemCount() != 1) {
+            return null;
+        }
+        SparseBooleanArray selected = mDelegate.getSelectedItemPositions();
+        for (int i = 0; i < selected.size(); i++) {
+            if (selected.valueAt(i)) {
+                return mDelegate.getItemAtPosition(selected.keyAt(i));
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/photos/MultiSelectGridFragment.java b/src/com/android/photos/MultiSelectGridFragment.java
new file mode 100644
index 0000000..dda9fe4
--- /dev/null
+++ b/src/com/android/photos/MultiSelectGridFragment.java
@@ -0,0 +1,348 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.AdapterView;
+import android.widget.GridView;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+
+public abstract class MultiSelectGridFragment extends Fragment
+        implements MultiChoiceManager.Delegate, AdapterView.OnItemClickListener {
+
+    final private Handler mHandler = new Handler();
+
+    final private Runnable mRequestFocus = new Runnable() {
+        @Override
+        public void run() {
+            mGrid.focusableViewAvailable(mGrid);
+        }
+    };
+
+    ListAdapter mAdapter;
+    GridView mGrid;
+    TextView mEmptyView;
+    View mProgressContainer;
+    View mGridContainer;
+    CharSequence mEmptyText;
+    boolean mGridShown;
+    MultiChoiceManager.Provider mHost;
+
+    public MultiSelectGridFragment() {
+    }
+
+    /**
+     * Provide default implementation to return a simple grid view. Subclasses
+     * can override to replace with their own layout. If doing so, the returned
+     * view hierarchy <em>must</em> have a GridView whose id is
+     * {@link android.R.id#grid android.R.id.list} and can optionally have a
+     * sibling text view id {@link android.R.id#empty android.R.id.empty} that
+     * is to be shown when the grid is empty.
+     */
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.multigrid_content, container, false);
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        mHost = (MultiChoiceManager.Provider) activity;
+        if (mGrid != null) {
+            mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager());
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mHost = null;
+    }
+
+    /**
+     * Attach to grid view once the view hierarchy has been created.
+     */
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        ensureGrid();
+    }
+
+    /**
+     * Detach from grid view.
+     */
+    @Override
+    public void onDestroyView() {
+        mHandler.removeCallbacks(mRequestFocus);
+        mGrid = null;
+        mGridShown = false;
+        mEmptyView = null;
+        mProgressContainer = mGridContainer = null;
+        super.onDestroyView();
+    }
+
+    /**
+     * This method will be called when an item in the grid is selected.
+     * Subclasses should override. Subclasses can call
+     * getGridView().getItemAtPosition(position) if they need to access the data
+     * associated with the selected item.
+     *
+     * @param g The GridView where the click happened
+     * @param v The view that was clicked within the GridView
+     * @param position The position of the view in the grid
+     * @param id The id of the item that was clicked
+     */
+    public void onGridItemClick(GridView g, View v, int position, long id) {
+    }
+
+    /**
+     * Provide the cursor for the grid view.
+     */
+    public void setAdapter(ListAdapter adapter) {
+        boolean hadAdapter = mAdapter != null;
+        mAdapter = adapter;
+        if (mGrid != null) {
+            mGrid.setAdapter(adapter);
+            if (!mGridShown && !hadAdapter) {
+                // The grid was hidden, and previously didn't have an
+                // adapter. It is now time to show it.
+                setGridShown(true, getView().getWindowToken() != null);
+            }
+        }
+    }
+
+    /**
+     * Set the currently selected grid item to the specified position with the
+     * adapter's data
+     *
+     * @param position
+     */
+    public void setSelection(int position) {
+        ensureGrid();
+        mGrid.setSelection(position);
+    }
+
+    /**
+     * Get the position of the currently selected grid item.
+     */
+    public int getSelectedItemPosition() {
+        ensureGrid();
+        return mGrid.getSelectedItemPosition();
+    }
+
+    /**
+     * Get the cursor row ID of the currently selected grid item.
+     */
+    public long getSelectedItemId() {
+        ensureGrid();
+        return mGrid.getSelectedItemId();
+    }
+
+    /**
+     * Get the activity's grid view widget.
+     */
+    public GridView getGridView() {
+        ensureGrid();
+        return mGrid;
+    }
+
+    /**
+     * The default content for a MultiSelectGridFragment has a TextView that can
+     * be shown when the grid is empty. If you would like to have it shown, call
+     * this method to supply the text it should use.
+     */
+    public void setEmptyText(CharSequence text) {
+        ensureGrid();
+        if (mEmptyView == null) {
+            return;
+        }
+        mEmptyView.setText(text);
+        if (mEmptyText == null) {
+            mGrid.setEmptyView(mEmptyView);
+        }
+        mEmptyText = text;
+    }
+
+    /**
+     * Control whether the grid is being displayed. You can make it not
+     * displayed if you are waiting for the initial data to show in it. During
+     * this time an indeterminate progress indicator will be shown instead.
+     * <p>
+     * Applications do not normally need to use this themselves. The default
+     * behavior of MultiSelectGridFragment is to start with the grid not being
+     * shown, only showing it once an adapter is given with
+     * {@link #setAdapter(ListAdapter)}. If the grid at that point had not been
+     * shown, when it does get shown it will be do without the user ever seeing
+     * the hidden state.
+     *
+     * @param shown If true, the grid view is shown; if false, the progress
+     *            indicator. The initial value is true.
+     */
+    public void setGridShown(boolean shown) {
+        setGridShown(shown, true);
+    }
+
+    /**
+     * Like {@link #setGridShown(boolean)}, but no animation is used when
+     * transitioning from the previous state.
+     */
+    public void setGridShownNoAnimation(boolean shown) {
+        setGridShown(shown, false);
+    }
+
+    /**
+     * Control whether the grid is being displayed. You can make it not
+     * displayed if you are waiting for the initial data to show in it. During
+     * this time an indeterminate progress indicator will be shown instead.
+     *
+     * @param shown If true, the grid view is shown; if false, the progress
+     *            indicator. The initial value is true.
+     * @param animate If true, an animation will be used to transition to the
+     *            new state.
+     */
+    private void setGridShown(boolean shown, boolean animate) {
+        ensureGrid();
+        if (mProgressContainer == null) {
+            throw new IllegalStateException("Can't be used with a custom content view");
+        }
+        if (mGridShown == shown) {
+            return;
+        }
+        mGridShown = shown;
+        if (shown) {
+            if (animate) {
+                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_out));
+                mGridContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_in));
+            } else {
+                mProgressContainer.clearAnimation();
+                mGridContainer.clearAnimation();
+            }
+            mProgressContainer.setVisibility(View.GONE);
+            mGridContainer.setVisibility(View.VISIBLE);
+        } else {
+            if (animate) {
+                mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_in));
+                mGridContainer.startAnimation(AnimationUtils.loadAnimation(
+                        getActivity(), android.R.anim.fade_out));
+            } else {
+                mProgressContainer.clearAnimation();
+                mGridContainer.clearAnimation();
+            }
+            mProgressContainer.setVisibility(View.VISIBLE);
+            mGridContainer.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Get the ListAdapter associated with this activity's GridView.
+     */
+    public ListAdapter getAdapter() {
+        return mGrid.getAdapter();
+    }
+
+    private void ensureGrid() {
+        if (mGrid != null) {
+            return;
+        }
+        View root = getView();
+        if (root == null) {
+            throw new IllegalStateException("Content view not yet created");
+        }
+        if (root instanceof GridView) {
+            mGrid = (GridView) root;
+        } else {
+            View empty = root.findViewById(android.R.id.empty);
+            if (empty != null && empty instanceof TextView) {
+                mEmptyView = (TextView) empty;
+            }
+            mProgressContainer = root.findViewById(R.id.progressContainer);
+            mGridContainer = root.findViewById(R.id.gridContainer);
+            View rawGridView = root.findViewById(android.R.id.list);
+            if (!(rawGridView instanceof GridView)) {
+                throw new RuntimeException(
+                        "Content has view with id attribute 'android.R.id.list' "
+                                + "that is not a GridView class");
+            }
+            mGrid = (GridView) rawGridView;
+            if (mGrid == null) {
+                throw new RuntimeException(
+                        "Your content must have a GridView whose id attribute is " +
+                                "'android.R.id.list'");
+            }
+            if (mEmptyView != null) {
+                mGrid.setEmptyView(mEmptyView);
+            }
+        }
+        mGridShown = true;
+        mGrid.setOnItemClickListener(this);
+        mGrid.setMultiChoiceModeListener(mHost.getMultiChoiceManager());
+        if (mAdapter != null) {
+            ListAdapter adapter = mAdapter;
+            mAdapter = null;
+            setAdapter(adapter);
+        } else {
+            // We are starting without an adapter, so assume we won't
+            // have our data right away and start with the progress indicator.
+            if (mProgressContainer != null) {
+                setGridShown(false, false);
+            }
+        }
+        mHandler.post(mRequestFocus);
+    }
+
+    @Override
+    public Object getItemAtPosition(int position) {
+        return getAdapter().getItem(position);
+    }
+
+    @Override
+    public Object getPathForItemAtPosition(int position) {
+        return getPathForItem(getItemAtPosition(position));
+    }
+
+    @Override
+    public SparseBooleanArray getSelectedItemPositions() {
+        return mGrid.getCheckedItemPositions();
+    }
+
+    @Override
+    public int getSelectedItemCount() {
+        return mGrid.getCheckedItemCount();
+    }
+
+    public abstract Object getPathForItem(Object item);
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+        onGridItemClick((GridView) parent, v, position, id);
+    }
+}
diff --git a/src/com/android/photos/PhotoFragment.java b/src/com/android/photos/PhotoFragment.java
new file mode 100644
index 0000000..3be6313
--- /dev/null
+++ b/src/com/android/photos/PhotoFragment.java
@@ -0,0 +1,25 @@
+/*
+ * 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.photos;
+
+import android.app.Fragment;
+
+
+public class PhotoFragment extends Fragment {
+
+}
diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java
new file mode 100644
index 0000000..961fd0b
--- /dev/null
+++ b/src/com/android/photos/PhotoSetFragment.java
@@ -0,0 +1,133 @@
+/*
+ * 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.photos;
+
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.GridView;
+
+import com.android.gallery3d.app.Gallery;
+import com.android.photos.adapters.PhotoThumbnailAdapter;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.shims.MediaItemsLoader;
+
+import java.util.ArrayList;
+
+public class PhotoSetFragment extends MultiSelectGridFragment implements LoaderCallbacks<Cursor> {
+
+    private static final int LOADER_PHOTOSET = 1;
+
+    private LoaderCompatShim<Cursor> mLoaderCompatShim;
+    private PhotoThumbnailAdapter mAdapter;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Context context = getActivity();
+        mAdapter = new PhotoThumbnailAdapter(context);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View root = super.onCreateView(inflater, container, savedInstanceState);
+        getLoaderManager().initLoader(LOADER_PHOTOSET, null, this);
+        return root;
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        // TODO: Remove once UI stabilizes
+        getGridView().setColumnWidth(MediaItemsLoader.getThumbnailSize());
+    }
+
+    @Override
+    public void onGridItemClick(GridView g, View v, int position, long id) {
+        if (mLoaderCompatShim == null) {
+            // Not fully initialized yet, discard
+            return;
+        }
+        Cursor item = (Cursor) getItemAtPosition(position);
+        Uri uri = mLoaderCompatShim.uriForItem(item);
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.setClass(getActivity(), Gallery.class);
+        startActivity(intent);
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        // TODO: Switch to PhotoSetLoader
+        MediaItemsLoader loader = new MediaItemsLoader(getActivity());
+        mLoaderCompatShim = loader;
+        mAdapter.setDrawableFactory(mLoaderCompatShim);
+        return loader;
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader,
+            Cursor data) {
+        mAdapter.swapCursor(data);
+        setAdapter(mAdapter);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+    }
+
+    @Override
+    public int getItemMediaType(Object item) {
+        return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE);
+    }
+
+    @Override
+    public int getItemSupportedOperations(Object item) {
+        return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS);
+    }
+
+    private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1);
+    @Override
+    public ArrayList<Uri> getSubItemUrisForItem(Object item) {
+        mSubItemUriTemp.clear();
+        mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item));
+        return mSubItemUriTemp;
+    }
+
+    @Override
+    public void deleteItemWithPath(Object itemPath) {
+        mLoaderCompatShim.deleteItemWithPath(itemPath);
+    }
+
+    @Override
+    public Uri getItemUri(Object item) {
+        return mLoaderCompatShim.uriForItem((Cursor) item);
+    }
+
+    @Override
+    public Object getPathForItem(Object item) {
+        return mLoaderCompatShim.getPathForItem((Cursor) item);
+    }
+}
diff --git a/src/com/android/photos/SelectionManager.java b/src/com/android/photos/SelectionManager.java
new file mode 100644
index 0000000..9bfb9be
--- /dev/null
+++ b/src/com/android/photos/SelectionManager.java
@@ -0,0 +1,184 @@
+/*
+ * 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.photos;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcAdapter.CreateBeamUrisCallback;
+import android.nfc.NfcEvent;
+import android.provider.MediaStore.Files.FileColumns;
+import android.widget.ShareActionProvider;
+
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.util.GalleryUtils;
+
+import java.util.ArrayList;
+
+public class SelectionManager {
+    private Activity mActivity;
+    private NfcAdapter mNfcAdapter;
+    private SelectedUriSource mUriSource;
+    private Intent mShareIntent = new Intent();
+
+    public interface SelectedUriSource {
+        public ArrayList<Uri> getSelectedShareableUris();
+    }
+
+    public SelectionManager(Activity activity) {
+        mActivity = activity;
+        if (ApiHelper.AT_LEAST_16) {
+            mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity);
+            mNfcAdapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
+                @Override
+                public Uri[] createBeamUris(NfcEvent arg0) {
+                 // This will have been preceded by a call to onItemSelectedStateChange
+                    if (mCachedShareableUris == null) return null;
+                    return mCachedShareableUris.toArray(
+                            new Uri[mCachedShareableUris.size()]);
+                }
+            }, mActivity);
+        }
+    }
+
+    public void setSelectedUriSource(SelectedUriSource source) {
+        mUriSource = source;
+    }
+
+    private int mSelectedTotalCount = 0;
+    private int mSelectedShareableCount = 0;
+    private int mSelectedShareableImageCount = 0;
+    private int mSelectedShareableVideoCount = 0;
+    private int mSelectedDeletableCount = 0;
+    private int mSelectedEditableCount = 0;
+    private int mSelectedCroppableCount = 0;
+    private int mSelectedSetableCount = 0;
+    private int mSelectedTrimmableCount = 0;
+    private int mSelectedMuteableCount = 0;
+
+    private ArrayList<Uri> mCachedShareableUris = null;
+
+    public void onItemSelectedStateChanged(ShareActionProvider share,
+            int itemType, int itemSupportedOperations, boolean selected) {
+        int increment = selected ? 1 : -1;
+
+        mSelectedTotalCount += increment;
+        mCachedShareableUris = null;
+
+        if ((itemSupportedOperations & MediaObject.SUPPORT_DELETE) > 0) {
+            mSelectedDeletableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_EDIT) > 0) {
+            mSelectedEditableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_CROP) > 0) {
+            mSelectedCroppableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_SETAS) > 0) {
+            mSelectedSetableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_TRIM) > 0) {
+            mSelectedTrimmableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_MUTE) > 0) {
+            mSelectedMuteableCount += increment;
+        }
+        if ((itemSupportedOperations & MediaObject.SUPPORT_SHARE) > 0) {
+            mSelectedShareableCount += increment;
+            if (itemType == FileColumns.MEDIA_TYPE_IMAGE) {
+                mSelectedShareableImageCount += increment;
+            } else if (itemType == FileColumns.MEDIA_TYPE_VIDEO) {
+                mSelectedShareableVideoCount += increment;
+            }
+        }
+
+        mShareIntent.removeExtra(Intent.EXTRA_STREAM);
+        if (mSelectedShareableCount == 0) {
+            mShareIntent.setAction(null).setType(null);
+        } else if (mSelectedShareableCount >= 1) {
+            mCachedShareableUris = mUriSource.getSelectedShareableUris();
+            if (mCachedShareableUris.size() == 0) {
+                mShareIntent.setAction(null).setType(null);
+            } else {
+                if (mSelectedShareableImageCount == mSelectedShareableCount) {
+                    mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE);
+                } else if (mSelectedShareableVideoCount == mSelectedShareableCount) {
+                    mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO);
+                } else {
+                    mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL);
+                }
+                if (mCachedShareableUris.size() == 1) {
+                    mShareIntent.setAction(Intent.ACTION_SEND);
+                    mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0));
+                } else {
+                    mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE);
+                    mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris);
+                }
+            }
+        }
+        share.setShareIntent(mShareIntent);
+    }
+
+    public int getSupportedOperations() {
+        if (mSelectedTotalCount == 0) {
+            return 0;
+        }
+        int supported = 0;
+        if (mSelectedTotalCount == 1) {
+            if (mSelectedCroppableCount == 1) {
+                supported |= MediaObject.SUPPORT_CROP;
+            }
+            if (mSelectedEditableCount == 1) {
+                supported |= MediaObject.SUPPORT_EDIT;
+            }
+            if (mSelectedSetableCount == 1) {
+                supported |= MediaObject.SUPPORT_SETAS;
+            }
+            if (mSelectedTrimmableCount == 1) {
+                supported |= MediaObject.SUPPORT_TRIM;
+            }
+            if (mSelectedMuteableCount == 1) {
+                supported |= MediaObject.SUPPORT_MUTE;
+            }
+        }
+        if (mSelectedDeletableCount == mSelectedTotalCount) {
+            supported |= MediaObject.SUPPORT_DELETE;
+        }
+        if (mSelectedShareableCount > 0) {
+            supported |= MediaObject.SUPPORT_SHARE;
+        }
+        return supported;
+    }
+
+    public void onClearSelection() {
+        mSelectedTotalCount = 0;
+        mSelectedShareableCount = 0;
+        mSelectedShareableImageCount = 0;
+        mSelectedShareableVideoCount = 0;
+        mSelectedDeletableCount = 0;
+        mSelectedEditableCount = 0;
+        mSelectedCroppableCount = 0;
+        mSelectedSetableCount = 0;
+        mSelectedTrimmableCount = 0;
+        mSelectedMuteableCount = 0;
+        mCachedShareableUris = null;
+        mShareIntent.removeExtra(Intent.EXTRA_STREAM);
+        mShareIntent.setAction(null).setType(null);
+    }
+}
diff --git a/src/com/android/photos/adapters/AlbumSetCursorAdapter.java b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java
new file mode 100644
index 0000000..ab99cde
--- /dev/null
+++ b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.photos.adapters;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.text.format.DateFormat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.AlbumSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+
+import java.util.Date;
+
+public class AlbumSetCursorAdapter extends CursorAdapter {
+
+    private LoaderCompatShim<Cursor> mDrawableFactory;
+
+    public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+        mDrawableFactory = factory;
+    }
+
+    public AlbumSetCursorAdapter(Context context) {
+        super(context, null, false);
+    }
+
+    @Override
+    public void bindView(View v, Context context, Cursor cursor) {
+        TextView titleTextView = (TextView) v.findViewById(
+                R.id.album_set_item_title);
+        titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE));
+
+        TextView countTextView = (TextView) v.findViewById(
+                R.id.album_set_item_count);
+        int count = cursor.getInt(AlbumSetLoader.INDEX_COUNT);
+        countTextView.setText(context.getResources().getQuantityString(
+                R.plurals.number_of_photos, count, count));
+
+        ImageView thumbImageView = (ImageView) v.findViewById(
+                R.id.album_set_item_image);
+        Drawable recycle = thumbImageView.getDrawable();
+        Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+        if (recycle != drawable) {
+            thumbImageView.setImageDrawable(drawable);
+        }
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return LayoutInflater.from(context).inflate(
+                R.layout.album_set_item, parent, false);
+    }
+}
diff --git a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java
new file mode 100644
index 0000000..1190b8c
--- /dev/null
+++ b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.photos.adapters;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+import android.widget.ImageView;
+
+import com.android.gallery3d.R;
+import com.android.photos.data.PhotoSetLoader;
+import com.android.photos.shims.LoaderCompatShim;
+import com.android.photos.views.GalleryThumbnailView.GalleryThumbnailAdapter;
+
+
+public class PhotoThumbnailAdapter extends CursorAdapter implements GalleryThumbnailAdapter {
+    private LayoutInflater mInflater;
+    private LoaderCompatShim<Cursor> mDrawableFactory;
+
+    public PhotoThumbnailAdapter(Context context) {
+        super(context, null, false);
+        mInflater = LayoutInflater.from(context);
+    }
+
+    public void setDrawableFactory(LoaderCompatShim<Cursor> factory) {
+        mDrawableFactory = factory;
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        ImageView iv = (ImageView) view.findViewById(R.id.thumbnail);
+        Drawable recycle = iv.getDrawable();
+        Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle);
+        if (recycle != drawable) {
+            iv.setImageDrawable(drawable);
+        }
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        View view = mInflater.inflate(R.layout.photo_set_item, parent, false);
+        return view;
+    }
+
+    @Override
+    public float getIntrinsicAspectRatio(int position) {
+        Cursor cursor = getItem(position);
+        float width = cursor.getInt(PhotoSetLoader.INDEX_WIDTH);
+        float height = cursor.getInt(PhotoSetLoader.INDEX_HEIGHT);
+        return width / height;
+    }
+
+    @Override
+    public Cursor getItem(int position) {
+        return (Cursor) super.getItem(position);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java
new file mode 100644
index 0000000..9404732
--- /dev/null
+++ b/src/com/android/photos/data/AlbumSetLoader.java
@@ -0,0 +1,54 @@
+package com.android.photos.data;
+
+import android.database.MatrixCursor;
+
+
+public class AlbumSetLoader {
+    public static final int INDEX_ID = 0;
+    public static final int INDEX_TITLE = 1;
+    public static final int INDEX_TIMESTAMP = 2;
+    public static final int INDEX_THUMBNAIL_URI = 3;
+    public static final int INDEX_THUMBNAIL_WIDTH = 4;
+    public static final int INDEX_THUMBNAIL_HEIGHT = 5;
+    public static final int INDEX_COUNT_PENDING_UPLOAD = 6;
+    public static final int INDEX_COUNT = 7;
+    public static final int INDEX_SUPPORTED_OPERATIONS = 8;
+
+    public static final String[] PROJECTION = {
+        "_id",
+        "title",
+        "timestamp",
+        "thumb_uri",
+        "thumb_width",
+        "thumb_height",
+        "count_pending_upload",
+        "_count",
+        "supported_operations"
+    };
+    public static final MatrixCursor MOCK = createRandomCursor(30);
+
+    private static MatrixCursor createRandomCursor(int count) {
+        MatrixCursor c = new MatrixCursor(PROJECTION, count);
+        for (int i = 0; i < count; i++) {
+            c.addRow(createRandomRow());
+        }
+        return c;
+    }
+
+    private static Object[] createRandomRow() {
+        double random = Math.random();
+        int id = (int) (500 * random);
+        Object[] row = {
+            id,
+            "Fun times " + id,
+            (long) (System.currentTimeMillis() * random),
+            null,
+            0,
+            0,
+            (random < .3 ? 1 : 0),
+            1,
+            0
+        };
+        return row;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java
new file mode 100644
index 0000000..a0ab410
--- /dev/null
+++ b/src/com/android/photos/data/BitmapDecoder.java
@@ -0,0 +1,122 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SynchronizedPool;
+
+import com.android.gallery3d.common.Utils;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * BitmapDecoder keeps a pool of temporary storage to reuse for decoding
+ * bitmaps. It also simplifies the multi-stage decoding required to efficiently
+ * use GalleryBitmapPool. The static methods decode and decodeFile can be used
+ * to decode a bitmap from GalleryBitmapPool. The bitmap may be returned
+ * directly to GalleryBitmapPool or use the put method here when the bitmap is
+ * ready to be recycled.
+ */
+public class BitmapDecoder {
+    private static final String TAG = BitmapDecoder.class.getSimpleName();
+    private static final int POOL_SIZE = 4;
+    private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024;
+    private static final int HEADER_MAX_SIZE = 16 * 1024;
+
+    private static final Pool<BitmapFactory.Options> sOptions =
+            new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE);
+
+    public static Bitmap decode(InputStream in) {
+        BitmapFactory.Options opts = getOptions();
+        try {
+            if (!in.markSupported()) {
+                in = new BufferedInputStream(in);
+            }
+            opts.inJustDecodeBounds = true;
+            in.mark(HEADER_MAX_SIZE);
+            BitmapFactory.decodeStream(in, null, opts);
+            in.reset();
+            opts.inJustDecodeBounds = false;
+            GalleryBitmapPool pool = GalleryBitmapPool.getInstance();
+            Bitmap reuseBitmap = pool.get(opts.outWidth, opts.outHeight);
+            opts.inBitmap = reuseBitmap;
+            Bitmap decodedBitmap = BitmapFactory.decodeStream(in, null, opts);
+            if (reuseBitmap != null && decodedBitmap != reuseBitmap) {
+                pool.put(reuseBitmap);
+            }
+            return decodedBitmap;
+        } catch (IOException e) {
+            Log.e(TAG, "Could not decode stream to bitmap", e);
+            return null;
+        } finally {
+            Utils.closeSilently(in);
+            release(opts);
+        }
+    }
+
+    public static Bitmap decode(File in) {
+        return decodeFile(in.toString());
+    }
+
+    public static Bitmap decodeFile(String in) {
+        BitmapFactory.Options opts = getOptions();
+        try {
+            opts.inJustDecodeBounds = true;
+            BitmapFactory.decodeFile(in, opts);
+            opts.inJustDecodeBounds = false;
+            GalleryBitmapPool pool = GalleryBitmapPool.getInstance();
+            Bitmap reuseBitmap = pool.get(opts.outWidth, opts.outHeight);
+            opts.inBitmap = reuseBitmap;
+            Bitmap decodedBitmap = BitmapFactory.decodeFile(in, opts);
+            if (reuseBitmap != null && decodedBitmap != reuseBitmap) {
+                pool.put(reuseBitmap);
+            }
+            return decodedBitmap;
+        } finally {
+            release(opts);
+        }
+    }
+
+    public static void put(Bitmap bitmap) {
+        GalleryBitmapPool.getInstance().put(bitmap);
+    }
+
+    private static BitmapFactory.Options getOptions() {
+        BitmapFactory.Options opts = sOptions.acquire();
+        if (opts == null) {
+            opts = new BitmapFactory.Options();
+            opts.inMutable = true;
+            opts.inPreferredConfig = Config.ARGB_8888;
+            opts.inSampleSize = 1;
+            opts.inTempStorage = new byte[TEMP_STORAGE_SIZE_BYTES];
+        }
+
+        return opts;
+    }
+
+    private static void release(BitmapFactory.Options opts) {
+        opts.inBitmap = null;
+        opts.inJustDecodeBounds = false;
+        sOptions.release(opts);
+    }
+}
diff --git a/src/com/android/photos/data/FileRetriever.java b/src/com/android/photos/data/FileRetriever.java
new file mode 100644
index 0000000..eb7686e
--- /dev/null
+++ b/src/com/android/photos/data/FileRetriever.java
@@ -0,0 +1,109 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import com.android.gallery3d.common.BitmapUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileRetriever implements MediaRetriever {
+    private static final String TAG = FileRetriever.class.getSimpleName();
+
+    @Override
+    public File getLocalFile(Uri contentUri) {
+        return new File(contentUri.getPath());
+    }
+
+    @Override
+    public MediaSize getFastImageSize(Uri contentUri, MediaSize size) {
+        if (isVideo(contentUri)) {
+            return null;
+        }
+        return MediaSize.TemporaryThumbnail;
+    }
+
+    @Override
+    public byte[] getTemporaryImage(Uri contentUri, MediaSize fastImageSize) {
+
+        try {
+            ExifInterface exif = new ExifInterface(contentUri.getPath());
+            if (exif.hasThumbnail()) {
+                return exif.getThumbnail();
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to load exif for " + contentUri);
+        }
+        return null;
+    }
+
+    @Override
+    public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) {
+        if (imageSize == MediaSize.Original) {
+            return false; // getLocalFile should always return the original.
+        }
+        if (imageSize == MediaSize.Thumbnail) {
+            File preview = MediaCache.getInstance().getCachedFile(contentUri, MediaSize.Preview);
+            if (preview != null) {
+                // Just downsample the preview, it is faster.
+                return MediaCacheUtils.downsample(preview, imageSize, tempFile);
+            }
+        }
+        File highRes = new File(contentUri.getPath());
+        boolean success;
+        if (!isVideo(contentUri)) {
+            success = MediaCacheUtils.downsample(highRes, imageSize, tempFile);
+        } else {
+            // Video needs to extract the bitmap.
+            Bitmap bitmap = BitmapUtils.createVideoThumbnail(highRes.getPath());
+            if (bitmap == null) {
+                return false;
+            } else if (imageSize == MediaSize.Thumbnail
+                    && !MediaCacheUtils.needsDownsample(bitmap, MediaSize.Preview)
+                    && MediaCacheUtils.writeToFile(bitmap, tempFile)) {
+                // Opportunistically save preview
+                MediaCache mediaCache = MediaCache.getInstance();
+                mediaCache.insertIntoCache(contentUri, MediaSize.Preview, tempFile);
+            }
+            // Now scale the image
+            success = MediaCacheUtils.downsample(bitmap, imageSize, tempFile);
+        }
+        return success;
+    }
+
+    @Override
+    public Uri normalizeUri(Uri contentUri, MediaSize size) {
+        return contentUri;
+    }
+
+    @Override
+    public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) {
+        return size;
+    }
+
+    private static boolean isVideo(Uri uri) {
+        MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
+        String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
+        String mimeType = mimeTypeMap.getMimeTypeFromExtension(extension);
+        return (mimeType != null && mimeType.startsWith("video/"));
+    }
+}
diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java
new file mode 100644
index 0000000..aca3e4b
--- /dev/null
+++ b/src/com/android/photos/data/GalleryBitmapPool.java
@@ -0,0 +1,126 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.util.Pools.Pool;
+import android.util.Pools.SynchronizedPool;
+
+import com.android.photos.data.SparseArrayBitmapPool.Node;
+
+public class GalleryBitmapPool {
+
+    private static final int CAPACITY_BYTES = 20971520;
+    private static final int POOL_INDEX_NONE = -1;
+    private static final int POOL_INDEX_SQUARE = 0;
+    private static final int POOL_INDEX_PHOTO = 1;
+    private static final int POOL_INDEX_MISC = 2;
+
+    private static final Point[] COMMON_PHOTO_ASPECT_RATIOS =
+        { new Point(4, 3), new Point(3, 2), new Point(16, 9) };
+
+    private int mCapacityBytes;
+    private SparseArrayBitmapPool [] mPools;
+    private Pool<Node> mSharedNodePool = new SynchronizedPool<Node>(128);
+
+    private GalleryBitmapPool(int capacityBytes) {
+        mPools = new SparseArrayBitmapPool[3];
+        mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mCapacityBytes = capacityBytes;
+    }
+
+    private static GalleryBitmapPool sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
+
+    public static GalleryBitmapPool getInstance() {
+        return sInstance;
+    }
+
+    private SparseArrayBitmapPool getPoolForDimensions(int width, int height) {
+        int index = getPoolIndexForDimensions(width, height);
+        if (index == POOL_INDEX_NONE) {
+            return null;
+        } else {
+            return mPools[index];
+        }
+    }
+
+    private int getPoolIndexForDimensions(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return POOL_INDEX_NONE;
+        }
+        if (width == height) {
+            return POOL_INDEX_SQUARE;
+        }
+        int min, max;
+        if (width > height) {
+            min = height;
+            max = width;
+        } else {
+            min = width;
+            max = height;
+        }
+        for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) {
+            if (min * ar.x == max * ar.y) {
+                return POOL_INDEX_PHOTO;
+            }
+        }
+        return POOL_INDEX_MISC;
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        int total = 0;
+        for (SparseArrayBitmapPool p : mPools) {
+            total += p.getSize();
+        }
+        return total;
+    }
+
+    public Bitmap get(int width, int height) {
+        SparseArrayBitmapPool pool = getPoolForDimensions(width, height);
+        if (pool == null) {
+            return null;
+        } else {
+            return pool.get(width, height);
+        }
+    }
+
+    public boolean put(Bitmap b) {
+        if (b == null || b.getConfig() != Bitmap.Config.ARGB_8888) {
+            return false;
+        }
+        SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight());
+        if (pool == null) {
+            b.recycle();
+            return false;
+        } else {
+            return pool.put(b);
+        }
+    }
+
+    public void clear() {
+        for (SparseArrayBitmapPool p : mPools) {
+            p.clear();
+        }
+    }
+}
diff --git a/src/com/android/photos/data/MediaCache.java b/src/com/android/photos/data/MediaCache.java
new file mode 100644
index 0000000..0952a40
--- /dev/null
+++ b/src/com/android/photos/data/MediaCache.java
@@ -0,0 +1,676 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+import com.android.photos.data.MediaCacheDatabase.Action;
+import com.android.photos.data.MediaRetriever.MediaSize;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+
+/**
+ * MediaCache keeps a cache of images, videos, thumbnails and previews. Calls to
+ * retrieve a specific media item are executed asynchronously. The caller has an
+ * option to receive a notification for lower resolution images that happen to
+ * be available prior to the one requested.
+ * <p>
+ * When an media item has been retrieved, the notification for it is called on a
+ * separate notifier thread. This thread should not be held for a long time so
+ * that other notifications may happen.
+ * </p>
+ * <p>
+ * Media items are uniquely identified by their content URIs. Each
+ * scheme/authority can offer its own MediaRetriever, running in its own thread.
+ * </p>
+ * <p>
+ * The MediaCache is an LRU cache, but does not allow the thumbnail cache to
+ * drop below a minimum size. This prevents browsing through original images to
+ * wipe out the thumbnails.
+ * </p>
+ */
+public class MediaCache {
+    static final String TAG = MediaCache.class.getSimpleName();
+    /** Subdirectory containing the image cache. */
+    static final String IMAGE_CACHE_SUBDIR = "image_cache";
+    /** File name extension to use for cached images. */
+    static final String IMAGE_EXTENSION = ".cache";
+    /** File name extension to use for temporary cached images while retrieving. */
+    static final String TEMP_IMAGE_EXTENSION = ".temp";
+
+    public static interface ImageReady {
+        void imageReady(InputStream bitmapInputStream);
+    }
+
+    public static interface OriginalReady {
+        void originalReady(File originalFile);
+    }
+
+    /** A Thread for each MediaRetriever */
+    private class ProcessQueue extends Thread {
+        private Queue<ProcessingJob> mQueue;
+
+        public ProcessQueue(Queue<ProcessingJob> queue) {
+            mQueue = queue;
+        }
+
+        @Override
+        public void run() {
+            while (mRunning) {
+                ProcessingJob status;
+                synchronized (mQueue) {
+                    while (mQueue.isEmpty()) {
+                        try {
+                            mQueue.wait();
+                        } catch (InterruptedException e) {
+                            if (!mRunning) {
+                                return;
+                            }
+                            Log.w(TAG, "Unexpected interruption", e);
+                        }
+                    }
+                    status = mQueue.remove();
+                }
+                processTask(status);
+            }
+        }
+    };
+
+    private interface NotifyReady {
+        void notifyReady();
+
+        void setFile(File file) throws FileNotFoundException;
+
+        boolean isPrefetch();
+    }
+
+    private static class NotifyOriginalReady implements NotifyReady {
+        private final OriginalReady mCallback;
+        private File mFile;
+
+        public NotifyOriginalReady(OriginalReady callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void notifyReady() {
+            if (mCallback != null) {
+                mCallback.originalReady(mFile);
+            }
+        }
+
+        @Override
+        public void setFile(File file) {
+            mFile = file;
+        }
+
+        @Override
+        public boolean isPrefetch() {
+            return mCallback == null;
+        }
+    }
+
+    private static class NotifyImageReady implements NotifyReady {
+        private final ImageReady mCallback;
+        private InputStream mInputStream;
+
+        public NotifyImageReady(ImageReady callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void notifyReady() {
+            if (mCallback != null) {
+                mCallback.imageReady(mInputStream);
+            }
+        }
+
+        @Override
+        public void setFile(File file) throws FileNotFoundException {
+            mInputStream = new FileInputStream(file);
+        }
+
+        public void setBytes(byte[] bytes) {
+            mInputStream = new ByteArrayInputStream(bytes);
+        }
+
+        @Override
+        public boolean isPrefetch() {
+            return mCallback == null;
+        }
+    }
+
+    /** A media item to be retrieved and its notifications. */
+    private static class ProcessingJob {
+        public ProcessingJob(Uri uri, MediaSize size, NotifyReady complete,
+                NotifyImageReady lowResolution) {
+            this.contentUri = uri;
+            this.size = size;
+            this.complete = complete;
+            this.lowResolution = lowResolution;
+        }
+        public Uri contentUri;
+        public MediaSize size;
+        public NotifyImageReady lowResolution;
+        public NotifyReady complete;
+    }
+
+    private boolean mRunning = true;
+    private static MediaCache sInstance;
+    private File mCacheDir;
+    private Context mContext;
+    private Queue<NotifyReady> mCallbacks = new LinkedList<NotifyReady>();
+    private Map<String, MediaRetriever> mRetrievers = new HashMap<String, MediaRetriever>();
+    private Map<String, List<ProcessingJob>> mTasks = new HashMap<String, List<ProcessingJob>>();
+    private List<ProcessQueue> mProcessingThreads = new ArrayList<ProcessQueue>();
+    private MediaCacheDatabase mDatabaseHelper;
+    private long mTempImageNumber = 1;
+    private Object mTempImageNumberLock = new Object();
+
+    private long mMaxCacheSize = 40 * 1024 * 1024; // 40 MB
+    private long mMinThumbCacheSize = 4 * 1024 * 1024; // 4 MB
+    private long mCacheSize = -1;
+    private long mThumbCacheSize = -1;
+    private Object mCacheSizeLock = new Object();
+
+    private Action mNotifyCachedLowResolution = new Action() {
+        @Override
+        public void execute(Uri uri, long id, MediaSize size, Object parameter) {
+            ProcessingJob job = (ProcessingJob) parameter;
+            File file = createCacheImagePath(id);
+            addNotification(job.lowResolution, file);
+        }
+    };
+
+    private Action mMoveTempToCache = new Action() {
+        @Override
+        public void execute(Uri uri, long id, MediaSize size, Object parameter) {
+            File tempFile = (File) parameter;
+            File cacheFile = createCacheImagePath(id);
+            tempFile.renameTo(cacheFile);
+        }
+    };
+
+    private Action mDeleteFile = new Action() {
+        @Override
+        public void execute(Uri uri, long id, MediaSize size, Object parameter) {
+            File file = createCacheImagePath(id);
+            file.delete();
+            synchronized (mCacheSizeLock) {
+                if (mCacheSize != -1) {
+                    long length = (Long) parameter;
+                    mCacheSize -= length;
+                    if (size == MediaSize.Thumbnail) {
+                        mThumbCacheSize -= length;
+                    }
+                }
+            }
+        }
+    };
+
+    /** The thread used to make ImageReady and OriginalReady callbacks. */
+    private Thread mProcessNotifications = new Thread() {
+        @Override
+        public void run() {
+            while (mRunning) {
+                NotifyReady notifyImage;
+                synchronized (mCallbacks) {
+                    while (mCallbacks.isEmpty()) {
+                        try {
+                            mCallbacks.wait();
+                        } catch (InterruptedException e) {
+                            if (!mRunning) {
+                                return;
+                            }
+                            Log.w(TAG, "Unexpected Interruption, continuing");
+                        }
+                    }
+                    notifyImage = mCallbacks.remove();
+                }
+
+                notifyImage.notifyReady();
+            }
+        }
+    };
+
+    public static synchronized void initialize(Context context) {
+        if (sInstance == null) {
+            sInstance = new MediaCache(context);
+            MediaCacheUtils.initialize(context);
+        }
+    }
+
+    public static MediaCache getInstance() {
+        return sInstance;
+    }
+
+    public static synchronized void shutdown() {
+        sInstance.mRunning = false;
+        sInstance.mProcessNotifications.interrupt();
+        for (ProcessQueue processingThread : sInstance.mProcessingThreads) {
+            processingThread.interrupt();
+        }
+        sInstance = null;
+    }
+
+    private MediaCache(Context context) {
+        mDatabaseHelper = new MediaCacheDatabase(context);
+        mProcessNotifications.start();
+        mContext = context;
+    }
+
+    // This is used for testing.
+    public void setCacheDir(File cacheDir) {
+        cacheDir.mkdirs();
+        mCacheDir = cacheDir;
+    }
+
+    public File getCacheDir() {
+        synchronized (mContext) {
+            if (mCacheDir == null) {
+                String state = Environment.getExternalStorageState();
+                File baseDir;
+                if (Environment.MEDIA_MOUNTED.equals(state)) {
+                    baseDir = mContext.getExternalCacheDir();
+                } else {
+                    // Stored in internal cache
+                    baseDir = mContext.getCacheDir();
+                }
+                mCacheDir = new File(baseDir, IMAGE_CACHE_SUBDIR);
+                mCacheDir.mkdirs();
+            }
+            return mCacheDir;
+        }
+    }
+
+    /**
+     * Invalidates all cached images related to a given contentUri. This call
+     * doesn't complete until the images have been removed from the cache.
+     */
+    public void invalidate(Uri contentUri) {
+        mDatabaseHelper.delete(contentUri, mDeleteFile);
+    }
+
+    public void clearCacheDir() {
+        File[] cachedFiles = getCacheDir().listFiles();
+        if (cachedFiles != null) {
+            for (File cachedFile : cachedFiles) {
+                cachedFile.delete();
+            }
+        }
+    }
+
+    /**
+     * Add a MediaRetriever for a Uri scheme and authority. This MediaRetriever
+     * will be granted its own thread for retrieving images.
+     */
+    public void addRetriever(String scheme, String authority, MediaRetriever retriever) {
+        String differentiator = getDifferentiator(scheme, authority);
+        synchronized (mRetrievers) {
+            mRetrievers.put(differentiator, retriever);
+        }
+        synchronized (mTasks) {
+            LinkedList<ProcessingJob> queue = new LinkedList<ProcessingJob>();
+            mTasks.put(differentiator, queue);
+            new ProcessQueue(queue).start();
+        }
+    }
+
+    /**
+     * Retrieves a thumbnail. complete will be called when the thumbnail is
+     * available. If lowResolution is not null and a lower resolution thumbnail
+     * is available before the thumbnail, lowResolution will be called prior to
+     * complete. All callbacks will be made on a thread other than the calling
+     * thread.
+     *
+     * @param contentUri The URI for the full resolution image to search for.
+     * @param complete Callback for when the image has been retrieved.
+     * @param lowResolution If not null and a lower resolution image is
+     *            available prior to retrieving the thumbnail, this will be
+     *            called with the low resolution bitmap.
+     */
+    public void retrieveThumbnail(Uri contentUri, ImageReady complete, ImageReady lowResolution) {
+        addTask(contentUri, complete, lowResolution, MediaSize.Thumbnail);
+    }
+
+    /**
+     * Retrieves a preview. complete will be called when the preview is
+     * available. If lowResolution is not null and a lower resolution preview is
+     * available before the preview, lowResolution will be called prior to
+     * complete. All callbacks will be made on a thread other than the calling
+     * thread.
+     *
+     * @param contentUri The URI for the full resolution image to search for.
+     * @param complete Callback for when the image has been retrieved.
+     * @param lowResolution If not null and a lower resolution image is
+     *            available prior to retrieving the preview, this will be called
+     *            with the low resolution bitmap.
+     */
+    public void retrievePreview(Uri contentUri, ImageReady complete, ImageReady lowResolution) {
+        addTask(contentUri, complete, lowResolution, MediaSize.Preview);
+    }
+
+    /**
+     * Retrieves the original image or video. complete will be called when the
+     * media is available on the local file system. If lowResolution is not null
+     * and a lower resolution preview is available before the original,
+     * lowResolution will be called prior to complete. All callbacks will be
+     * made on a thread other than the calling thread.
+     *
+     * @param contentUri The URI for the full resolution image to search for.
+     * @param complete Callback for when the image has been retrieved.
+     * @param lowResolution If not null and a lower resolution image is
+     *            available prior to retrieving the preview, this will be called
+     *            with the low resolution bitmap.
+     */
+    public void retrieveOriginal(Uri contentUri, OriginalReady complete, ImageReady lowResolution) {
+        File localFile = getLocalFile(contentUri);
+        if (localFile != null) {
+            addNotification(new NotifyOriginalReady(complete), localFile);
+        } else {
+            NotifyImageReady notifyLowResolution = (lowResolution == null) ? null
+                    : new NotifyImageReady(lowResolution);
+            addTask(contentUri, new NotifyOriginalReady(complete), notifyLowResolution,
+                    MediaSize.Original);
+        }
+    }
+
+    /**
+     * Looks for an already cached media at a specific size.
+     *
+     * @param contentUri The original media item content URI
+     * @param size The target size to search for in the cache
+     * @return The cached file location or null if it is not cached.
+     */
+    public File getCachedFile(Uri contentUri, MediaSize size) {
+        Long cachedId = mDatabaseHelper.getCached(contentUri, size);
+        File file = null;
+        if (cachedId != null) {
+            file = createCacheImagePath(cachedId);
+            if (!file.exists()) {
+                mDatabaseHelper.delete(contentUri, size, mDeleteFile);
+                file = null;
+            }
+        }
+        return file;
+    }
+
+    /**
+     * Inserts a media item into the cache.
+     *
+     * @param contentUri The original media item URI.
+     * @param size The size of the media item to store in the cache.
+     * @param tempFile The temporary file where the image is stored. This file
+     *            will no longer exist after executing this method.
+     * @return The new location, in the cache, of the media item or null if it
+     *         wasn't possible to move into the cache.
+     */
+    public File insertIntoCache(Uri contentUri, MediaSize size, File tempFile) {
+        long fileSize = tempFile.length();
+        if (fileSize == 0) {
+            return null;
+        }
+        File cacheFile = null;
+        SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
+        // Ensure that this step is atomic
+        db.beginTransaction();
+        try {
+            Long id = mDatabaseHelper.getCached(contentUri, size);
+            if (id != null) {
+                cacheFile = createCacheImagePath(id);
+                if (tempFile.renameTo(cacheFile)) {
+                    mDatabaseHelper.updateLength(id, fileSize);
+                } else {
+                    Log.w(TAG, "Could not update cached file with " + tempFile);
+                    tempFile.delete();
+                    cacheFile = null;
+                }
+            } else {
+                ensureFreeCacheSpace(tempFile.length(), size);
+                id = mDatabaseHelper.insert(contentUri, size, mMoveTempToCache, tempFile);
+                cacheFile = createCacheImagePath(id);
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return cacheFile;
+    }
+
+    /**
+     * For testing purposes.
+     */
+    public void setMaxCacheSize(long maxCacheSize) {
+        synchronized (mCacheSizeLock) {
+            mMaxCacheSize = maxCacheSize;
+            mMinThumbCacheSize = mMaxCacheSize / 10;
+            mCacheSize = -1;
+            mThumbCacheSize = -1;
+        }
+    }
+
+    private File createCacheImagePath(long id) {
+        return new File(getCacheDir(), String.valueOf(id) + IMAGE_EXTENSION);
+    }
+
+    private void addTask(Uri contentUri, ImageReady complete, ImageReady lowResolution,
+            MediaSize size) {
+        NotifyReady notifyComplete = new NotifyImageReady(complete);
+        NotifyImageReady notifyLowResolution = null;
+        if (lowResolution != null) {
+            notifyLowResolution = new NotifyImageReady(lowResolution);
+        }
+        addTask(contentUri, notifyComplete, notifyLowResolution, size);
+    }
+
+    private void addTask(Uri contentUri, NotifyReady complete, NotifyImageReady lowResolution,
+            MediaSize size) {
+        MediaRetriever retriever = getMediaRetriever(contentUri);
+        Uri uri = retriever.normalizeUri(contentUri, size);
+        if (uri == null) {
+            throw new IllegalArgumentException("No MediaRetriever for " + contentUri);
+        }
+        size = retriever.normalizeMediaSize(uri, size);
+
+        File cachedFile = getCachedFile(uri, size);
+        if (cachedFile != null) {
+            addNotification(complete, cachedFile);
+            return;
+        }
+        String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority());
+        synchronized (mTasks) {
+            List<ProcessingJob> tasks = mTasks.get(differentiator);
+            if (tasks == null) {
+                throw new IllegalArgumentException("Cannot find retriever for: " + uri);
+            }
+            synchronized (tasks) {
+                ProcessingJob job = new ProcessingJob(uri, size, complete, lowResolution);
+                if (complete.isPrefetch()) {
+                    tasks.add(job);
+                } else {
+                    int index = tasks.size() - 1;
+                    while (index >= 0 && tasks.get(index).complete.isPrefetch()) {
+                        index--;
+                    }
+                    tasks.add(index + 1, job);
+                }
+                tasks.notifyAll();
+            }
+        }
+    }
+
+    private MediaRetriever getMediaRetriever(Uri uri) {
+        String differentiator = getDifferentiator(uri.getScheme(), uri.getAuthority());
+        MediaRetriever retriever;
+        synchronized (mRetrievers) {
+            retriever = mRetrievers.get(differentiator);
+        }
+        if (retriever == null) {
+            throw new IllegalArgumentException("No MediaRetriever for " + uri);
+        }
+        return retriever;
+    }
+
+    private File getLocalFile(Uri uri) {
+        MediaRetriever retriever = getMediaRetriever(uri);
+        File localFile = null;
+        if (retriever != null) {
+            localFile = retriever.getLocalFile(uri);
+        }
+        return localFile;
+    }
+
+    private MediaSize getFastImageSize(Uri uri, MediaSize size) {
+        MediaRetriever retriever = getMediaRetriever(uri);
+        return retriever.getFastImageSize(uri, size);
+    }
+
+    private boolean isFastImageBetter(MediaSize fastImageType, MediaSize size) {
+        if (fastImageType == null) {
+            return false;
+        }
+        if (size == null) {
+            return true;
+        }
+        return fastImageType.isBetterThan(size);
+    }
+
+    private byte[] getTemporaryImage(Uri uri, MediaSize fastImageType) {
+        MediaRetriever retriever = getMediaRetriever(uri);
+        return retriever.getTemporaryImage(uri, fastImageType);
+    }
+
+    private void processTask(ProcessingJob job) {
+        File cachedFile = getCachedFile(job.contentUri, job.size);
+        if (cachedFile != null) {
+            addNotification(job.complete, cachedFile);
+            return;
+        }
+
+        boolean hasLowResolution = job.lowResolution != null;
+        if (hasLowResolution) {
+            MediaSize cachedSize = mDatabaseHelper.executeOnBestCached(job.contentUri, job.size,
+                    mNotifyCachedLowResolution);
+            MediaSize fastImageSize = getFastImageSize(job.contentUri, job.size);
+            if (isFastImageBetter(fastImageSize, cachedSize)) {
+                if (fastImageSize.isTemporary()) {
+                    byte[] bytes = getTemporaryImage(job.contentUri, fastImageSize);
+                    if (bytes != null) {
+                        addNotification(job.lowResolution, bytes);
+                    }
+                } else {
+                    File lowFile = getMedia(job.contentUri, fastImageSize);
+                    if (lowFile != null) {
+                        addNotification(job.lowResolution, lowFile);
+                    }
+                }
+            }
+        }
+
+        // Now get the full size desired
+        File fullSizeFile = getMedia(job.contentUri, job.size);
+        if (fullSizeFile != null) {
+            addNotification(job.complete, fullSizeFile);
+        }
+    }
+
+    private void addNotification(NotifyReady callback, File file) {
+        try {
+            callback.setFile(file);
+            synchronized (mCallbacks) {
+                mCallbacks.add(callback);
+                mCallbacks.notifyAll();
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Unable to read file " + file, e);
+        }
+    }
+
+    private void addNotification(NotifyImageReady callback, byte[] bytes) {
+        callback.setBytes(bytes);
+        synchronized (mCallbacks) {
+            mCallbacks.add(callback);
+            mCallbacks.notifyAll();
+        }
+    }
+
+    private File getMedia(Uri uri, MediaSize size) {
+        long imageNumber;
+        synchronized (mTempImageNumberLock) {
+            imageNumber = mTempImageNumber++;
+        }
+        File tempFile = new File(getCacheDir(), String.valueOf(imageNumber) + TEMP_IMAGE_EXTENSION);
+        MediaRetriever retriever = getMediaRetriever(uri);
+        boolean retrieved = retriever.getMedia(uri, size, tempFile);
+        File cachedFile = null;
+        if (retrieved) {
+            ensureFreeCacheSpace(tempFile.length(), size);
+            long id = mDatabaseHelper.insert(uri, size, mMoveTempToCache, tempFile);
+            cachedFile = createCacheImagePath(id);
+        }
+        return cachedFile;
+    }
+
+    private static String getDifferentiator(String scheme, String authority) {
+        if (authority == null) {
+            return scheme;
+        }
+        StringBuilder differentiator = new StringBuilder(scheme);
+        differentiator.append(':');
+        differentiator.append(authority);
+        return differentiator.toString();
+    }
+
+    private void ensureFreeCacheSpace(long size, MediaSize mediaSize) {
+        synchronized (mCacheSizeLock) {
+            if (mCacheSize == -1 || mThumbCacheSize == -1) {
+                mCacheSize = mDatabaseHelper.getCacheSize();
+                mThumbCacheSize = mDatabaseHelper.getThumbnailCacheSize();
+                if (mCacheSize == -1 || mThumbCacheSize == -1) {
+                    Log.e(TAG, "Can't determine size of the image cache");
+                    return;
+                }
+            }
+            mCacheSize += size;
+            if (mediaSize == MediaSize.Thumbnail) {
+                mThumbCacheSize += size;
+            }
+            if (mCacheSize > mMaxCacheSize) {
+                shrinkCacheLocked();
+            }
+        }
+    }
+
+    private void shrinkCacheLocked() {
+        long deleteSize = mMinThumbCacheSize;
+        boolean includeThumbnails = (mThumbCacheSize - deleteSize) > mMinThumbCacheSize;
+        mDatabaseHelper.deleteOldCached(includeThumbnails, deleteSize, mDeleteFile);
+    }
+}
diff --git a/src/com/android/photos/data/MediaCacheDatabase.java b/src/com/android/photos/data/MediaCacheDatabase.java
new file mode 100644
index 0000000..c92ac0f
--- /dev/null
+++ b/src/com/android/photos/data/MediaCacheDatabase.java
@@ -0,0 +1,286 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import com.android.photos.data.MediaRetriever.MediaSize;
+
+import java.io.File;
+
+class MediaCacheDatabase extends SQLiteOpenHelper {
+    public static final int DB_VERSION = 1;
+    public static final String DB_NAME = "mediacache.db";
+
+    /** Internal database table used for the media cache */
+    public static final String TABLE = "media_cache";
+
+    private static interface Columns extends BaseColumns {
+        /** The Content URI of the original image. */
+        public static final String URI = "uri";
+        /** MediaSize.getValue() values. */
+        public static final String MEDIA_SIZE = "media_size";
+        /** The last time this image was queried. */
+        public static final String LAST_ACCESS = "last_access";
+        /** The image size in bytes. */
+        public static final String SIZE_IN_BYTES = "size";
+    }
+
+    static interface Action {
+        void execute(Uri uri, long id, MediaSize size, Object parameter);
+    }
+
+    private static final String[] PROJECTION_ID = {
+        Columns._ID,
+    };
+
+    private static final String[] PROJECTION_CACHED = {
+        Columns._ID, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES,
+    };
+
+    private static final String[] PROJECTION_CACHE_SIZE = {
+        "SUM(" + Columns.SIZE_IN_BYTES + ")"
+    };
+
+    private static final String[] PROJECTION_DELETE_OLD = {
+        Columns._ID, Columns.URI, Columns.MEDIA_SIZE, Columns.SIZE_IN_BYTES, Columns.LAST_ACCESS,
+    };
+
+    public static final String CREATE_TABLE = "CREATE TABLE " + TABLE + "("
+            + Columns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+            + Columns.URI + " TEXT NOT NULL,"
+            + Columns.MEDIA_SIZE + " INTEGER NOT NULL,"
+            + Columns.LAST_ACCESS + " INTEGER NOT NULL,"
+            + Columns.SIZE_IN_BYTES + " INTEGER NOT NULL,"
+            + "UNIQUE(" + Columns.URI + ", " + Columns.MEDIA_SIZE + "))";
+
+    public static final String DROP_TABLE = "DROP TABLE IF EXISTS " + TABLE;
+
+    public static final String WHERE_THUMBNAIL = Columns.MEDIA_SIZE + " = "
+            + MediaSize.Thumbnail.getValue();
+
+    public static final String WHERE_NOT_THUMBNAIL = Columns.MEDIA_SIZE + " <> "
+            + MediaSize.Thumbnail.getValue();
+
+    public static final String WHERE_CLEAR_CACHE = Columns.LAST_ACCESS + " <= ?";
+
+    public static final String WHERE_CLEAR_CACHE_LARGE = WHERE_CLEAR_CACHE + " AND "
+            + WHERE_NOT_THUMBNAIL;
+
+    static class QueryCacheResults {
+        public QueryCacheResults(long id, int sizeVal) {
+            this.id = id;
+            this.size = MediaSize.fromInteger(sizeVal);
+        }
+        public long id;
+        public MediaSize size;
+    }
+
+    public MediaCacheDatabase(Context context) {
+        super(context, DB_NAME, null, DB_VERSION);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(CREATE_TABLE);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        db.execSQL(DROP_TABLE);
+        onCreate(db);
+        MediaCache.getInstance().clearCacheDir();
+    }
+
+    public Long getCached(Uri uri, MediaSize size) {
+        String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?";
+        SQLiteDatabase db = getWritableDatabase();
+        String[] whereArgs = {
+                uri.toString(), String.valueOf(size.getValue()),
+        };
+        Cursor cursor = db.query(TABLE, PROJECTION_ID, where, whereArgs, null, null, null);
+        Long id = null;
+        if (cursor.moveToNext()) {
+            id = cursor.getLong(0);
+        }
+        cursor.close();
+        if (id != null) {
+            String[] updateArgs = {
+                id.toString()
+            };
+            ContentValues values = new ContentValues();
+            values.put(Columns.LAST_ACCESS, System.currentTimeMillis());
+            db.beginTransaction();
+            try {
+                db.update(TABLE, values, Columns._ID + " = ?", updateArgs);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+        }
+        return id;
+    }
+
+    public MediaSize executeOnBestCached(Uri uri, MediaSize size, Action action) {
+        String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " < ?";
+        String orderBy = Columns.MEDIA_SIZE + " DESC";
+        SQLiteDatabase db = getReadableDatabase();
+        String[] whereArgs = {
+                uri.toString(), String.valueOf(size.getValue()),
+        };
+        Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, orderBy);
+        MediaSize bestSize = null;
+        if (cursor.moveToNext()) {
+            long id = cursor.getLong(0);
+            bestSize = MediaSize.fromInteger(cursor.getInt(1));
+            long fileSize = cursor.getLong(2);
+            action.execute(uri, id, bestSize, fileSize);
+        }
+        cursor.close();
+        return bestSize;
+    }
+
+    public long insert(Uri uri, MediaSize size, Action action, File tempFile) {
+        SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+        try {
+            ContentValues values = new ContentValues();
+            values.put(Columns.LAST_ACCESS, System.currentTimeMillis());
+            values.put(Columns.MEDIA_SIZE, size.getValue());
+            values.put(Columns.URI, uri.toString());
+            values.put(Columns.SIZE_IN_BYTES, tempFile.length());
+            long id = db.insert(TABLE, null, values);
+            if (id != -1) {
+                action.execute(uri, id, size, tempFile);
+                db.setTransactionSuccessful();
+            }
+            return id;
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void updateLength(long id, long fileSize) {
+        ContentValues values = new ContentValues();
+        values.put(Columns.SIZE_IN_BYTES, fileSize);
+        String[] whereArgs = {
+            String.valueOf(id)
+        };
+        SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+        try {
+            db.update(TABLE, values, Columns._ID + " = ?", whereArgs);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void delete(Uri uri, MediaSize size, Action action) {
+        String where = Columns.URI + " = ? AND " + Columns.MEDIA_SIZE + " = ?";
+        String[] whereArgs = {
+                uri.toString(), String.valueOf(size.getValue()),
+        };
+        deleteRows(uri, where, whereArgs, action);
+    }
+
+    public void delete(Uri uri, Action action) {
+        String where = Columns.URI + " = ?";
+        String[] whereArgs = {
+            uri.toString()
+        };
+        deleteRows(uri, where, whereArgs, action);
+    }
+
+    private void deleteRows(Uri uri, String where, String[] whereArgs, Action action) {
+        SQLiteDatabase db = getWritableDatabase();
+        // Make this an atomic operation
+        db.beginTransaction();
+        Cursor cursor = db.query(TABLE, PROJECTION_CACHED, where, whereArgs, null, null, null);
+        while (cursor.moveToNext()) {
+            long id = cursor.getLong(0);
+            MediaSize size = MediaSize.fromInteger(cursor.getInt(1));
+            long length = cursor.getLong(2);
+            action.execute(uri, id, size, length);
+        }
+        cursor.close();
+        try {
+            db.delete(TABLE, where, whereArgs);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void deleteOldCached(boolean includeThumbnails, long deleteSize, Action action) {
+        String where = includeThumbnails ? null : WHERE_NOT_THUMBNAIL;
+        long lastAccess = 0;
+        SQLiteDatabase db = getWritableDatabase();
+        db.beginTransaction();
+        try {
+            Cursor cursor = db.query(TABLE, PROJECTION_DELETE_OLD, where, null, null, null,
+                    Columns.LAST_ACCESS);
+            while (cursor.moveToNext()) {
+                long id = cursor.getLong(0);
+                String uri = cursor.getString(1);
+                MediaSize size = MediaSize.fromInteger(cursor.getInt(2));
+                long length = cursor.getLong(3);
+                long imageLastAccess = cursor.getLong(4);
+
+                if (imageLastAccess != lastAccess && deleteSize < 0) {
+                    break; // We've deleted enough.
+                }
+                lastAccess = imageLastAccess;
+                action.execute(Uri.parse(uri), id, size, length);
+                deleteSize -= length;
+            }
+            cursor.close();
+            String[] whereArgs = {
+                String.valueOf(lastAccess),
+            };
+            String whereDelete = includeThumbnails ? WHERE_CLEAR_CACHE : WHERE_CLEAR_CACHE_LARGE;
+            db.delete(TABLE, whereDelete, whereArgs);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public long getCacheSize() {
+        return getCacheSize(null);
+    }
+
+    public long getThumbnailCacheSize() {
+        return getCacheSize(WHERE_THUMBNAIL);
+    }
+
+    private long getCacheSize(String where) {
+        SQLiteDatabase db = getReadableDatabase();
+        Cursor cursor = db.query(TABLE, PROJECTION_CACHE_SIZE, where, null, null, null, null);
+        long size = -1;
+        if (cursor.moveToNext()) {
+            size = cursor.getLong(0);
+        }
+        cursor.close();
+        return size;
+    }
+}
diff --git a/src/com/android/photos/data/MediaCacheUtils.java b/src/com/android/photos/data/MediaCacheUtils.java
new file mode 100644
index 0000000..e3ccd14
--- /dev/null
+++ b/src/com/android/photos/data/MediaCacheUtils.java
@@ -0,0 +1,167 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
+import android.util.Log;
+import android.util.Pools.SimplePool;
+import android.util.Pools.SynchronizedPool;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DecodeUtils;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.photos.data.MediaRetriever.MediaSize;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class MediaCacheUtils {
+    private static final String TAG = MediaCacheUtils.class.getSimpleName();
+    private static int QUALITY = 80;
+    private static final int BUFFER_SIZE = 4096;
+    private static final SimplePool<byte[]> mBufferPool = new SynchronizedPool<byte[]>(5);
+
+    private static final JobContext sJobStub = new JobContext() {
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public void setCancelListener(CancelListener listener) {
+        }
+
+        @Override
+        public boolean setMode(int mode) {
+            return true;
+        }
+    };
+
+    private static int mTargetThumbnailSize;
+    private static int mTargetPreviewSize;
+
+    public static void initialize(Context context) {
+        Resources resources = context.getResources();
+        mTargetThumbnailSize = resources.getDimensionPixelSize(R.dimen.size_thumbnail);
+        mTargetPreviewSize = resources.getDimensionPixelSize(R.dimen.size_preview);
+    }
+
+    public static int getTargetSize(MediaSize size) {
+        return (size == MediaSize.Thumbnail) ? mTargetThumbnailSize : mTargetPreviewSize;
+    }
+
+    public static boolean downsample(File inBitmap, MediaSize targetSize, File outBitmap) {
+        if (MediaSize.Original == targetSize) {
+            return false; // MediaCache should use the local path for this.
+        }
+        int size = getTargetSize(targetSize);
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+        // TODO: remove unnecessary job context from DecodeUtils.
+        Bitmap bitmap = DecodeUtils.decodeThumbnail(sJobStub, inBitmap.getPath(), options, size,
+                MediaItem.TYPE_THUMBNAIL);
+        boolean success = (bitmap != null);
+        if (success) {
+            success = writeAndRecycle(bitmap, outBitmap);
+        }
+        return success;
+    }
+
+    public static boolean downsample(Bitmap inBitmap, MediaSize size, File outBitmap) {
+        if (MediaSize.Original == size) {
+            return false; // MediaCache should use the local path for this.
+        }
+        int targetSize = getTargetSize(size);
+        boolean success;
+        if (!needsDownsample(inBitmap, size)) {
+            success = writeAndRecycle(inBitmap, outBitmap);
+        } else {
+            float maxDimension = Math.max(inBitmap.getWidth(), inBitmap.getHeight());
+            float scale = targetSize / maxDimension;
+            int targetWidth = Math.round(scale * inBitmap.getWidth());
+            int targetHeight = Math.round(scale * inBitmap.getHeight());
+            Bitmap scaled = Bitmap.createScaledBitmap(inBitmap, targetWidth, targetHeight, false);
+            success = writeAndRecycle(scaled, outBitmap);
+            inBitmap.recycle();
+        }
+        return success;
+    }
+
+    public static boolean extractImageFromVideo(File inVideo, File outBitmap) {
+        Bitmap bitmap = BitmapUtils.createVideoThumbnail(inVideo.getPath());
+        return writeAndRecycle(bitmap, outBitmap);
+    }
+
+    public static boolean needsDownsample(Bitmap bitmap, MediaSize size) {
+        if (size == MediaSize.Original) {
+            return false;
+        }
+        int targetSize = getTargetSize(size);
+        int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+        return maxDimension > (targetSize * 4 / 3);
+    }
+
+    public static boolean writeAndRecycle(Bitmap bitmap, File outBitmap) {
+        boolean success = writeToFile(bitmap, outBitmap);
+        bitmap.recycle();
+        return success;
+    }
+
+    public static boolean writeToFile(Bitmap bitmap, File outBitmap) {
+        boolean success = false;
+        try {
+            FileOutputStream out = new FileOutputStream(outBitmap);
+            success = bitmap.compress(CompressFormat.JPEG, QUALITY, out);
+            out.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Couldn't write bitmap to cache", e);
+            // success is already false
+        }
+        return success;
+    }
+
+    public static int copyStream(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = mBufferPool.acquire();
+        if (buffer == null) {
+            buffer = new byte[BUFFER_SIZE];
+        }
+        try {
+            int totalWritten = 0;
+            int bytesRead;
+            while ((bytesRead = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, bytesRead);
+                totalWritten += bytesRead;
+            }
+            return totalWritten;
+        } finally {
+            Utils.closeSilently(in);
+            Utils.closeSilently(out);
+            mBufferPool.release(buffer);
+        }
+    }
+}
diff --git a/src/com/android/photos/data/MediaRetriever.java b/src/com/android/photos/data/MediaRetriever.java
new file mode 100644
index 0000000..f383e5f
--- /dev/null
+++ b/src/com/android/photos/data/MediaRetriever.java
@@ -0,0 +1,129 @@
+/*
+ * 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.photos.data;
+
+import android.net.Uri;
+
+import java.io.File;
+
+public interface MediaRetriever {
+    public enum MediaSize {
+        TemporaryThumbnail(5), Thumbnail(10), TemporaryPreview(15), Preview(20), Original(30);
+
+        private final int mValue;
+
+        private MediaSize(int value) {
+            mValue = value;
+        }
+
+        public int getValue() {
+            return mValue;
+        }
+
+        static MediaSize fromInteger(int value) {
+            switch (value) {
+                case 10:
+                    return MediaSize.Thumbnail;
+                case 20:
+                    return MediaSize.Preview;
+                case 30:
+                    return MediaSize.Original;
+                default:
+                    throw new IllegalArgumentException();
+            }
+        }
+
+        public boolean isBetterThan(MediaSize that) {
+            return mValue > that.mValue;
+        }
+
+        public boolean isTemporary() {
+            return this == TemporaryThumbnail || this == TemporaryPreview;
+        }
+    }
+
+    /**
+     * Returns the local File for the given Uri. If the image is not stored
+     * locally, null should be returned. The image should not be retrieved if it
+     * isn't already available.
+     *
+     * @param contentUri The media URI to search for.
+     * @return The local File of the image if it is available or null if it
+     *         isn't.
+     */
+    File getLocalFile(Uri contentUri);
+
+    /**
+     * Returns the fast access image type for a given image size, if supported.
+     * This image should be smaller than size and should be quick to retrieve.
+     * It does not have to obey the expected aspect ratio.
+     *
+     * @param contentUri The original media Uri.
+     * @param size The target size to search for a fast-access image.
+     * @return The fast image type supported for the given image size or null of
+     *         no fast image is supported.
+     */
+    MediaSize getFastImageSize(Uri contentUri, MediaSize size);
+
+    /**
+     * Returns a byte array containing the contents of the fast temporary image
+     * for a given image size. For example, a thumbnail may be smaller or of a
+     * different aspect ratio than the generated thumbnail.
+     *
+     * @param contentUri The original media Uri.
+     * @param temporarySize The target media size. Guaranteed to be a MediaSize
+     *            for which isTemporary() returns true.
+     * @return A byte array of contents for for the given contentUri and
+     *         fastImageType. null can be retrieved if the quick retrieval
+     *         fails.
+     */
+    byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize);
+
+    /**
+     * Retrieves an image and saves it to a file.
+     *
+     * @param contentUri The original media Uri.
+     * @param size The target media size.
+     * @param tempFile The file to write the bitmap to.
+     * @return <code>true</code> on success.
+     */
+    boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile);
+
+    /**
+     * Normalizes a URI that may have additional parameters. It is fine to
+     * return contentUri. This is executed on the calling thread, so it must be
+     * a fast access operation and cannot depend, for example, on I/O.
+     *
+     * @param contentUri The URI to normalize
+     * @param size The size of the image being requested
+     * @return The normalized URI representation of contentUri.
+     */
+    Uri normalizeUri(Uri contentUri, MediaSize size);
+
+    /**
+     * Normalize the MediaSize for a given URI. Typically the size returned
+     * would be the passed-in size. Some URIs may only have one size used and
+     * should be treaded as Thumbnails, for example. This is executed on the
+     * calling thread, so it must be a fast access operation and cannot depend,
+     * for example, on I/O.
+     *
+     * @param contentUri The URI for the size being normalized.
+     * @param size The size to be normalized.
+     * @return The normalized size of the given URI.
+     */
+    MediaSize normalizeMediaSize(Uri contentUri, MediaSize size);
+}
diff --git a/src/com/android/photos/data/NotificationWatcher.java b/src/com/android/photos/data/NotificationWatcher.java
new file mode 100644
index 0000000..9041c23
--- /dev/null
+++ b/src/com/android/photos/data/NotificationWatcher.java
@@ -0,0 +1,55 @@
+/*
+ * 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.photos.data;
+
+import android.net.Uri;
+
+import com.android.photos.data.PhotoProvider.ChangeNotification;
+
+import java.util.ArrayList;
+
+/**
+ * Used for capturing notifications from PhotoProvider without relying on
+ * ContentResolver. MockContentResolver does not allow sending notification to
+ * ContentObservers, so PhotoProvider allows this alternative for testing.
+ */
+public class NotificationWatcher implements ChangeNotification {
+    private ArrayList<Uri> mUris = new ArrayList<Uri>();
+    private boolean mSyncToNetwork = false;
+
+    @Override
+    public void notifyChange(Uri uri, boolean syncToNetwork) {
+        mUris.add(uri);
+        mSyncToNetwork = mSyncToNetwork || syncToNetwork;
+    }
+
+    public boolean isNotified(Uri uri) {
+        return mUris.contains(uri);
+    }
+
+    public int notificationCount() {
+        return mUris.size();
+    }
+
+    public boolean syncToNetwork() {
+        return mSyncToNetwork;
+    }
+
+    public void reset() {
+        mUris.clear();
+        mSyncToNetwork = false;
+    }
+}
diff --git a/src/com/android/photos/data/PhotoDatabase.java b/src/com/android/photos/data/PhotoDatabase.java
new file mode 100644
index 0000000..1f15c5b
--- /dev/null
+++ b/src/com/android/photos/data/PhotoDatabase.java
@@ -0,0 +1,195 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Used in PhotoProvider to create and access the database containing
+ * information about photo and video information stored on the server.
+ */
+public class PhotoDatabase extends SQLiteOpenHelper {
+    @SuppressWarnings("unused")
+    private static final String TAG = PhotoDatabase.class.getSimpleName();
+    static final int DB_VERSION = 2;
+
+    private static final String SQL_CREATE_TABLE = "CREATE TABLE ";
+
+    private static final String[][] CREATE_PHOTO = {
+        { Photos._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+        // Photos.ACCOUNT_ID is a foreign key to Accounts._ID
+        { Photos.ACCOUNT_ID, "INTEGER NOT NULL" },
+        { Photos.WIDTH, "INTEGER NOT NULL" },
+        { Photos.HEIGHT, "INTEGER NOT NULL" },
+        { Photos.DATE_TAKEN, "INTEGER NOT NULL" },
+        // Photos.ALBUM_ID is a foreign key to Albums._ID
+        { Photos.ALBUM_ID, "INTEGER" },
+        { Photos.MIME_TYPE, "TEXT NOT NULL" },
+        { Photos.TITLE, "TEXT" },
+        { Photos.DATE_MODIFIED, "INTEGER" },
+        { Photos.ROTATION, "INTEGER" },
+    };
+
+    private static final String[][] CREATE_ALBUM = {
+        { Albums._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+        // Albums.ACCOUNT_ID is a foreign key to Accounts._ID
+        { Albums.ACCOUNT_ID, "INTEGER NOT NULL" },
+        // Albums.PARENT_ID is a foreign key to Albums._ID
+        { Albums.PARENT_ID, "INTEGER" },
+        { Albums.ALBUM_TYPE, "TEXT" },
+        { Albums.VISIBILITY, "INTEGER NOT NULL" },
+        { Albums.LOCATION_STRING, "TEXT" },
+        { Albums.TITLE, "TEXT NOT NULL" },
+        { Albums.SUMMARY, "TEXT" },
+        { Albums.DATE_PUBLISHED, "INTEGER" },
+        { Albums.DATE_MODIFIED, "INTEGER" },
+        createUniqueConstraint(Albums.PARENT_ID, Albums.TITLE),
+    };
+
+    private static final String[][] CREATE_METADATA = {
+        { Metadata._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+        // Metadata.PHOTO_ID is a foreign key to Photos._ID
+        { Metadata.PHOTO_ID, "INTEGER NOT NULL" },
+        { Metadata.KEY, "TEXT NOT NULL" },
+        { Metadata.VALUE, "TEXT NOT NULL" },
+        createUniqueConstraint(Metadata.PHOTO_ID, Metadata.KEY),
+    };
+
+    private static final String[][] CREATE_ACCOUNT = {
+        { Accounts._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+        { Accounts.ACCOUNT_NAME, "TEXT NOT NULL" },
+    };
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        createTable(db, Accounts.TABLE, getAccountTableDefinition());
+        createTable(db, Albums.TABLE, getAlbumTableDefinition());
+        createTable(db, Photos.TABLE, getPhotoTableDefinition());
+        createTable(db, Metadata.TABLE, getMetadataTableDefinition());
+    }
+
+    public PhotoDatabase(Context context, String dbName, int dbVersion) {
+        super(context, dbName, null, dbVersion);
+    }
+
+    public PhotoDatabase(Context context, String dbName) {
+        super(context, dbName, null, DB_VERSION);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        recreate(db);
+    }
+
+    @Override
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        recreate(db);
+    }
+
+    private void recreate(SQLiteDatabase db) {
+        dropTable(db, Metadata.TABLE);
+        dropTable(db, Photos.TABLE);
+        dropTable(db, Albums.TABLE);
+        dropTable(db, Accounts.TABLE);
+        onCreate(db);
+    }
+
+    protected List<String[]> getAlbumTableDefinition() {
+        return tableCreationStrings(CREATE_ALBUM);
+    }
+
+    protected List<String[]> getPhotoTableDefinition() {
+        return tableCreationStrings(CREATE_PHOTO);
+    }
+
+    protected List<String[]> getMetadataTableDefinition() {
+        return tableCreationStrings(CREATE_METADATA);
+    }
+
+    protected List<String[]> getAccountTableDefinition() {
+        return tableCreationStrings(CREATE_ACCOUNT);
+    }
+
+    protected static void createTable(SQLiteDatabase db, String table, List<String[]> columns) {
+        StringBuilder create = new StringBuilder(SQL_CREATE_TABLE);
+        create.append(table).append('(');
+        boolean first = true;
+        for (String[] column : columns) {
+            if (!first) {
+                create.append(',');
+            }
+            first = false;
+            for (String val: column) {
+                create.append(val).append(' ');
+            }
+        }
+        create.append(')');
+        db.beginTransaction();
+        try {
+            db.execSQL(create.toString());
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    protected static String[] createUniqueConstraint(String column1, String column2) {
+        return new String[] {
+                "UNIQUE(", column1, ",", column2, ")"
+        };
+    }
+
+    protected static List<String[]> tableCreationStrings(String[][] createTable) {
+        ArrayList<String[]> create = new ArrayList<String[]>(createTable.length);
+        for (String[] line: createTable) {
+            create.add(line);
+        }
+        return create;
+    }
+
+    protected static void addToTable(List<String[]> createTable, String[][] columns, String[][] constraints) {
+        if (columns != null) {
+            for (String[] column: columns) {
+                createTable.add(0, column);
+            }
+        }
+        if (constraints != null) {
+            for (String[] constraint: constraints) {
+                createTable.add(constraint);
+            }
+        }
+    }
+
+    protected static void dropTable(SQLiteDatabase db, String table) {
+        db.beginTransaction();
+        try {
+            db.execSQL("drop table if exists " + table);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+}
diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java
new file mode 100644
index 0000000..d4310ca
--- /dev/null
+++ b/src/com/android/photos/data/PhotoProvider.java
@@ -0,0 +1,536 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.provider.BaseColumns;
+
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.List;
+
+/**
+ * A provider that gives access to photo and video information for media stored
+ * on the server. Only media that is or will be put on the server will be
+ * accessed by this provider. Use Photos.CONTENT_URI to query all photos and
+ * videos. Use Albums.CONTENT_URI to query all albums. Use Metadata.CONTENT_URI
+ * to query metadata about a photo or video, based on the ID of the media. Use
+ * ImageCache.THUMBNAIL_CONTENT_URI, ImageCache.PREVIEW_CONTENT_URI, or
+ * ImageCache.ORIGINAL_CONTENT_URI to query the path of the thumbnail, preview,
+ * or original-sized image respectfully. <br/>
+ * To add or update metadata, use the update function rather than insert. All
+ * values for the metadata must be in the ContentValues, even if they are also
+ * in the selection. The selection and selectionArgs are not used when updating
+ * metadata. If the metadata values are null, the row will be deleted.
+ */
+public class PhotoProvider extends SQLiteContentProvider {
+    @SuppressWarnings("unused")
+    private static final String TAG = PhotoProvider.class.getSimpleName();
+
+    protected static final String DB_NAME = "photo.db";
+    public static final String AUTHORITY = PhotoProviderAuthority.AUTHORITY;
+    static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme("content").authority(AUTHORITY)
+            .build();
+
+    // Used to allow mocking out the change notification because
+    // MockContextResolver disallows system-wide notification.
+    public static interface ChangeNotification {
+        void notifyChange(Uri uri, boolean syncToNetwork);
+    }
+
+    /**
+     * Contains columns that can be accessed via Accounts.CONTENT_URI
+     */
+    public static interface Accounts extends BaseColumns {
+        /**
+         * Internal database table used for account information
+         */
+        public static final String TABLE = "accounts";
+        /**
+         * Content URI for account information
+         */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+        /**
+         * User name for this account.
+         */
+        public static final String ACCOUNT_NAME = "name";
+    }
+
+    /**
+     * Contains columns that can be accessed via Photos.CONTENT_URI.
+     */
+    public static interface Photos extends BaseColumns {
+        /**
+         * The image_type query parameter required for requesting a specific
+         * size of image.
+         */
+        public static final String MEDIA_SIZE_QUERY_PARAMETER = "media_size";
+
+        /** Internal database table used for basic photo information. */
+        public static final String TABLE = "photos";
+        /** Content URI for basic photo and video information. */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+        /** Long foreign key to Accounts._ID */
+        public static final String ACCOUNT_ID = "account_id";
+        /** Column name for the width of the original image. Integer value. */
+        public static final String WIDTH = "width";
+        /** Column name for the height of the original image. Integer value. */
+        public static final String HEIGHT = "height";
+        /**
+         * Column name for the date that the original image was taken. Long
+         * value indicating the milliseconds since epoch in the GMT time zone.
+         */
+        public static final String DATE_TAKEN = "date_taken";
+        /**
+         * Column name indicating the long value of the album id that this image
+         * resides in. Will be NULL if it it has not been uploaded to the
+         * server.
+         */
+        public static final String ALBUM_ID = "album_id";
+        /** The column name for the mime-type String. */
+        public static final String MIME_TYPE = "mime_type";
+        /** The title of the photo. String value. */
+        public static final String TITLE = "title";
+        /** The date the photo entry was last updated. Long value. */
+        public static final String DATE_MODIFIED = "date_modified";
+        /**
+         * The rotation of the photo in degrees, if rotation has not already
+         * been applied. Integer value.
+         */
+        public static final String ROTATION = "rotation";
+    }
+
+    /**
+     * Contains columns and Uri for accessing album information.
+     */
+    public static interface Albums extends BaseColumns {
+        /** Internal database table used album information. */
+        public static final String TABLE = "albums";
+        /** Content URI for album information. */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+
+        /** Long foreign key to Accounts._ID */
+        public static final String ACCOUNT_ID = "account_id";
+        /** Parent directory or null if this is in the root. */
+        public static final String PARENT_ID = "parent_id";
+        /** The type of album. Non-null, if album is auto-generated. String value. */
+        public static final String ALBUM_TYPE = "album_type";
+        /**
+         * Column name for the visibility level of the album. Can be any of the
+         * VISIBILITY_* values.
+         */
+        public static final String VISIBILITY = "visibility";
+        /** The user-specified location associated with the album. String value. */
+        public static final String LOCATION_STRING = "location_string";
+        /** The title of the album. String value. */
+        public static final String TITLE = "title";
+        /** A short summary of the contents of the album. String value. */
+        public static final String SUMMARY = "summary";
+        /** The date the album was created. Long value */
+        public static final String DATE_PUBLISHED = "date_published";
+        /** The date the album entry was last updated. Long value. */
+        public static final String DATE_MODIFIED = "date_modified";
+
+        // Privacy values for Albums.VISIBILITY
+        public static final int VISIBILITY_PRIVATE = 1;
+        public static final int VISIBILITY_SHARED = 2;
+        public static final int VISIBILITY_PUBLIC = 3;
+    }
+
+    /**
+     * Contains columns and Uri for accessing photo and video metadata
+     */
+    public static interface Metadata extends BaseColumns {
+        /** Internal database table used metadata information. */
+        public static final String TABLE = "metadata";
+        /** Content URI for photo and video metadata. */
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE);
+        /** Foreign key to photo_id. Long value. */
+        public static final String PHOTO_ID = "photo_id";
+        /** Metadata key. String value */
+        public static final String KEY = "key";
+        /**
+         * Metadata value. Type is based on key.
+         */
+        public static final String VALUE = "value";
+
+        /** A short summary of the photo. String value. */
+        public static final String KEY_SUMMARY = "summary";
+        /** The date the photo was added. Long value. */
+        public static final String KEY_PUBLISHED = "date_published";
+        /** The date the photo was last updated. Long value. */
+        public static final String KEY_DATE_UPDATED = "date_updated";
+        /** The size of the photo is bytes. Integer value. */
+        public static final String KEY_SIZE_IN_BTYES = "size";
+        /** The latitude associated with the photo. Double value. */
+        public static final String KEY_LATITUDE = "latitude";
+        /** The longitude associated with the photo. Double value. */
+        public static final String KEY_LONGITUDE = "longitude";
+
+        /** The make of the camera used. String value. */
+        public static final String KEY_EXIF_MAKE = ExifInterface.TAG_MAKE;
+        /** The model of the camera used. String value. */
+        public static final String KEY_EXIF_MODEL = ExifInterface.TAG_MODEL;;
+        /** The exposure time used. Float value. */
+        public static final String KEY_EXIF_EXPOSURE = ExifInterface.TAG_EXPOSURE_TIME;
+        /** Whether the flash was used. Boolean value. */
+        public static final String KEY_EXIF_FLASH = ExifInterface.TAG_FLASH;
+        /** The focal length used. Float value. */
+        public static final String KEY_EXIF_FOCAL_LENGTH = ExifInterface.TAG_FOCAL_LENGTH;
+        /** The fstop value used. Float value. */
+        public static final String KEY_EXIF_FSTOP = ExifInterface.TAG_APERTURE;
+        /** The ISO equivalent value used. Integer value. */
+        public static final String KEY_EXIF_ISO = ExifInterface.TAG_ISO;
+    }
+
+    // SQL used within this class.
+    protected static final String WHERE_ID = BaseColumns._ID + " = ?";
+    protected static final String WHERE_METADATA_ID = Metadata.PHOTO_ID + " = ? AND "
+            + Metadata.KEY + " = ?";
+
+    protected static final String SELECT_ALBUM_ID = "SELECT " + Albums._ID + " FROM "
+            + Albums.TABLE;
+    protected static final String SELECT_PHOTO_ID = "SELECT " + Photos._ID + " FROM "
+            + Photos.TABLE;
+    protected static final String SELECT_PHOTO_COUNT = "SELECT COUNT(*) FROM " + Photos.TABLE;
+    protected static final String DELETE_PHOTOS = "DELETE FROM " + Photos.TABLE;
+    protected static final String DELETE_METADATA = "DELETE FROM " + Metadata.TABLE;
+    protected static final String SELECT_METADATA_COUNT = "SELECT COUNT(*) FROM " + Metadata.TABLE;
+    protected static final String WHERE = " WHERE ";
+    protected static final String IN = " IN ";
+    protected static final String NESTED_SELECT_START = "(";
+    protected static final String NESTED_SELECT_END = ")";
+    protected static final String[] PROJECTION_COUNT = {
+        "COUNT(*)"
+    };
+
+    /**
+     * For selecting the mime-type for an image.
+     */
+    private static final String[] PROJECTION_MIME_TYPE = {
+        Photos.MIME_TYPE,
+    };
+
+    protected static final String[] BASE_COLUMNS_ID = {
+        BaseColumns._ID,
+    };
+
+    protected ChangeNotification mNotifier = null;
+    protected static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+    protected static final int MATCH_PHOTO = 1;
+    protected static final int MATCH_PHOTO_ID = 2;
+    protected static final int MATCH_ALBUM = 3;
+    protected static final int MATCH_ALBUM_ID = 4;
+    protected static final int MATCH_METADATA = 5;
+    protected static final int MATCH_METADATA_ID = 6;
+    protected static final int MATCH_ACCOUNT = 7;
+    protected static final int MATCH_ACCOUNT_ID = 8;
+
+    static {
+        sUriMatcher.addURI(AUTHORITY, Photos.TABLE, MATCH_PHOTO);
+        // match against Photos._ID
+        sUriMatcher.addURI(AUTHORITY, Photos.TABLE + "/#", MATCH_PHOTO_ID);
+        sUriMatcher.addURI(AUTHORITY, Albums.TABLE, MATCH_ALBUM);
+        // match against Albums._ID
+        sUriMatcher.addURI(AUTHORITY, Albums.TABLE + "/#", MATCH_ALBUM_ID);
+        sUriMatcher.addURI(AUTHORITY, Metadata.TABLE, MATCH_METADATA);
+        // match against metadata/<Metadata._ID>
+        sUriMatcher.addURI(AUTHORITY, Metadata.TABLE + "/#", MATCH_METADATA_ID);
+        sUriMatcher.addURI(AUTHORITY, Accounts.TABLE, MATCH_ACCOUNT);
+        // match against Accounts._ID
+        sUriMatcher.addURI(AUTHORITY, Accounts.TABLE + "/#", MATCH_ACCOUNT_ID);
+    }
+
+    @Override
+    public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+            boolean callerIsSyncAdapter) {
+        int match = matchUri(uri);
+        selection = addIdToSelection(match, selection);
+        selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+        return deleteCascade(uri, match, selection, selectionArgs);
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        Cursor cursor = query(uri, PROJECTION_MIME_TYPE, null, null, null);
+        String mimeType = null;
+        if (cursor.moveToNext()) {
+            mimeType = cursor.getString(0);
+        }
+        cursor.close();
+        return mimeType;
+    }
+
+    @Override
+    public Uri insertInTransaction(Uri uri, ContentValues values, boolean callerIsSyncAdapter) {
+        int match = matchUri(uri);
+        validateMatchTable(match);
+        String table = getTableFromMatch(match, uri);
+        SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+        Uri insertedUri = null;
+        long id = db.insert(table, null, values);
+        if (id != -1) {
+            // uri already matches the table.
+            insertedUri = ContentUris.withAppendedId(uri, id);
+            postNotifyUri(insertedUri);
+        }
+        return insertedUri;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, null);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder, CancellationSignal cancellationSignal) {
+        projection = replaceCount(projection);
+        int match = matchUri(uri);
+        selection = addIdToSelection(match, selection);
+        selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+        String table = getTableFromMatch(match, uri);
+        Cursor c = query(table, projection, selection, selectionArgs, sortOrder, cancellationSignal);
+        if (c != null) {
+            c.setNotificationUri(getContext().getContentResolver(), uri);
+        }
+        return c;
+    }
+
+    @Override
+    public int updateInTransaction(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs, boolean callerIsSyncAdapter) {
+        int match = matchUri(uri);
+        int rowsUpdated = 0;
+        SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+        if (match == MATCH_METADATA) {
+            rowsUpdated = modifyMetadata(db, values);
+        } else {
+            selection = addIdToSelection(match, selection);
+            selectionArgs = addIdToSelectionArgs(match, uri, selectionArgs);
+            String table = getTableFromMatch(match, uri);
+            rowsUpdated = db.update(table, values, selection, selectionArgs);
+        }
+        postNotifyUri(uri);
+        return rowsUpdated;
+    }
+
+    public void setMockNotification(ChangeNotification notification) {
+        mNotifier = notification;
+    }
+
+    protected static String addIdToSelection(int match, String selection) {
+        String where;
+        switch (match) {
+            case MATCH_PHOTO_ID:
+            case MATCH_ALBUM_ID:
+            case MATCH_METADATA_ID:
+                where = WHERE_ID;
+                break;
+            default:
+                return selection;
+        }
+        return DatabaseUtils.concatenateWhere(selection, where);
+    }
+
+    protected static String[] addIdToSelectionArgs(int match, Uri uri, String[] selectionArgs) {
+        String[] whereArgs;
+        switch (match) {
+            case MATCH_PHOTO_ID:
+            case MATCH_ALBUM_ID:
+            case MATCH_METADATA_ID:
+                whereArgs = new String[] {
+                    uri.getPathSegments().get(1),
+                };
+                break;
+            default:
+                return selectionArgs;
+        }
+        return DatabaseUtils.appendSelectionArgs(selectionArgs, whereArgs);
+    }
+
+    protected static String[] addMetadataKeysToSelectionArgs(String[] selectionArgs, Uri uri) {
+        List<String> segments = uri.getPathSegments();
+        String[] additionalArgs = {
+                segments.get(1),
+                segments.get(2),
+        };
+
+        return DatabaseUtils.appendSelectionArgs(selectionArgs, additionalArgs);
+    }
+
+    protected static String getTableFromMatch(int match, Uri uri) {
+        String table;
+        switch (match) {
+            case MATCH_PHOTO:
+            case MATCH_PHOTO_ID:
+                table = Photos.TABLE;
+                break;
+            case MATCH_ALBUM:
+            case MATCH_ALBUM_ID:
+                table = Albums.TABLE;
+                break;
+            case MATCH_METADATA:
+            case MATCH_METADATA_ID:
+                table = Metadata.TABLE;
+                break;
+            case MATCH_ACCOUNT:
+            case MATCH_ACCOUNT_ID:
+                table = Accounts.TABLE;
+                break;
+            default:
+                throw unknownUri(uri);
+        }
+        return table;
+    }
+
+    @Override
+    public SQLiteOpenHelper getDatabaseHelper(Context context) {
+        return new PhotoDatabase(context, DB_NAME);
+    }
+
+    private int modifyMetadata(SQLiteDatabase db, ContentValues values) {
+        int rowCount;
+        if (values.get(Metadata.VALUE) == null) {
+            String[] selectionArgs = {
+                    values.getAsString(Metadata.PHOTO_ID), values.getAsString(Metadata.KEY),
+            };
+            rowCount = db.delete(Metadata.TABLE, WHERE_METADATA_ID, selectionArgs);
+        } else {
+            long rowId = db.replace(Metadata.TABLE, null, values);
+            rowCount = (rowId == -1) ? 0 : 1;
+        }
+        return rowCount;
+    }
+
+    private int matchUri(Uri uri) {
+        int match = sUriMatcher.match(uri);
+        if (match == UriMatcher.NO_MATCH) {
+            throw unknownUri(uri);
+        }
+        return match;
+    }
+
+    @Override
+    protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
+        if (mNotifier != null) {
+            mNotifier.notifyChange(uri, syncToNetwork);
+        } else {
+            super.notifyChange(resolver, uri, syncToNetwork);
+        }
+    }
+
+    protected static IllegalArgumentException unknownUri(Uri uri) {
+        return new IllegalArgumentException("Unknown Uri format: " + uri);
+    }
+
+    protected static String nestWhere(String matchColumn, String table, String nestedWhere) {
+        String query = SQLiteQueryBuilder.buildQueryString(false, table, BASE_COLUMNS_ID,
+                nestedWhere, null, null, null, null);
+        return matchColumn + IN + NESTED_SELECT_START + query + NESTED_SELECT_END;
+    }
+
+    protected static String metadataSelectionFromPhotos(String where) {
+        return nestWhere(Metadata.PHOTO_ID, Photos.TABLE, where);
+    }
+
+    protected static String photoSelectionFromAlbums(String where) {
+        return nestWhere(Photos.ALBUM_ID, Albums.TABLE, where);
+    }
+
+    protected static String photoSelectionFromAccounts(String where) {
+        return nestWhere(Photos.ACCOUNT_ID, Accounts.TABLE, where);
+    }
+
+    protected static String albumSelectionFromAccounts(String where) {
+        return nestWhere(Albums.ACCOUNT_ID, Accounts.TABLE, where);
+    }
+
+    protected int deleteCascade(Uri uri, int match, String selection, String[] selectionArgs) {
+        switch (match) {
+            case MATCH_PHOTO:
+            case MATCH_PHOTO_ID:
+                deleteCascade(Metadata.CONTENT_URI, MATCH_METADATA,
+                        metadataSelectionFromPhotos(selection), selectionArgs);
+                break;
+            case MATCH_ALBUM:
+            case MATCH_ALBUM_ID:
+                deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO,
+                        photoSelectionFromAlbums(selection), selectionArgs);
+                break;
+            case MATCH_ACCOUNT:
+            case MATCH_ACCOUNT_ID:
+                deleteCascade(Photos.CONTENT_URI, MATCH_PHOTO,
+                        photoSelectionFromAccounts(selection), selectionArgs);
+                deleteCascade(Albums.CONTENT_URI, MATCH_ALBUM,
+                        albumSelectionFromAccounts(selection), selectionArgs);
+                break;
+        }
+        SQLiteDatabase db = getDatabaseHelper().getWritableDatabase();
+        String table = getTableFromMatch(match, uri);
+        int deleted = db.delete(table, selection, selectionArgs);
+        if (deleted > 0) {
+            postNotifyUri(uri);
+        }
+        return deleted;
+    }
+
+    private static void validateMatchTable(int match) {
+        switch (match) {
+            case MATCH_PHOTO:
+            case MATCH_ALBUM:
+            case MATCH_METADATA:
+            case MATCH_ACCOUNT:
+                break;
+            default:
+                throw new IllegalArgumentException("Operation not allowed on an existing row.");
+        }
+    }
+
+    protected Cursor query(String table, String[] columns, String selection,
+            String[] selectionArgs, String orderBy, CancellationSignal cancellationSignal) {
+        SQLiteDatabase db = getDatabaseHelper().getReadableDatabase();
+        if (ApiHelper.HAS_CANCELLATION_SIGNAL) {
+            return db.query(false, table, columns, selection, selectionArgs, null, null,
+                    orderBy, null, cancellationSignal);
+        } else {
+            return db.query(table, columns, selection, selectionArgs, null, null, orderBy);
+        }
+    }
+
+    protected static String[] replaceCount(String[] projection) {
+        if (projection != null && projection.length == 1
+                && BaseColumns._COUNT.equals(projection[0])) {
+            return PROJECTION_COUNT;
+        }
+        return projection;
+    }
+}
diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java
new file mode 100644
index 0000000..56c82c4
--- /dev/null
+++ b/src/com/android/photos/data/PhotoSetLoader.java
@@ -0,0 +1,115 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Files.FileColumns;
+
+import com.android.photos.drawables.DataUriThumbnailDrawable;
+import com.android.photos.shims.LoaderCompatShim;
+
+import java.util.ArrayList;
+
+public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> {
+
+    public static final String SUPPORTED_OPERATIONS = "supported_operations";
+
+    private static final Uri CONTENT_URI = Files.getContentUri("external");
+    public static final String[] PROJECTION = new String[] {
+        FileColumns._ID,
+        FileColumns.DATA,
+        FileColumns.WIDTH,
+        FileColumns.HEIGHT,
+        FileColumns.DATE_ADDED,
+        FileColumns.MEDIA_TYPE,
+        SUPPORTED_OPERATIONS,
+    };
+
+    private static final String SORT_ORDER = FileColumns.DATE_ADDED + " DESC";
+    private static final String SELECTION =
+            FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_IMAGE
+            + " OR "
+            + FileColumns.MEDIA_TYPE + " == " + FileColumns.MEDIA_TYPE_VIDEO;
+
+    public static final int INDEX_ID = 0;
+    public static final int INDEX_DATA = 1;
+    public static final int INDEX_WIDTH = 2;
+    public static final int INDEX_HEIGHT = 3;
+    public static final int INDEX_DATE_ADDED = 4;
+    public static final int INDEX_MEDIA_TYPE = 5;
+    public static final int INDEX_SUPPORTED_OPERATIONS = 6;
+
+    private static final Uri GLOBAL_CONTENT_URI = Uri.parse("content://" + MediaStore.AUTHORITY + "/external/");
+    private final ContentObserver mGlobalObserver = new ForceLoadContentObserver();
+
+    public PhotoSetLoader(Context context) {
+        super(context, CONTENT_URI, PROJECTION, SELECTION, null, SORT_ORDER);
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        getContext().getContentResolver().registerContentObserver(GLOBAL_CONTENT_URI,
+                true, mGlobalObserver);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        getContext().getContentResolver().unregisterContentObserver(mGlobalObserver);
+    }
+
+    @Override
+    public Drawable drawableForItem(Cursor item, Drawable recycle) {
+        DataUriThumbnailDrawable drawable = null;
+        if (recycle == null || !(recycle instanceof DataUriThumbnailDrawable)) {
+            drawable = new DataUriThumbnailDrawable();
+        } else {
+            drawable = (DataUriThumbnailDrawable) recycle;
+        }
+        drawable.setImage(item.getString(INDEX_DATA),
+                item.getInt(INDEX_WIDTH), item.getInt(INDEX_HEIGHT));
+        return drawable;
+    }
+
+    @Override
+    public Uri uriForItem(Cursor item) {
+        return null;
+    }
+
+    @Override
+    public ArrayList<Uri> urisForSubItems(Cursor item) {
+        return null;
+    }
+
+    @Override
+    public void deleteItemWithPath(Object path) {
+
+    }
+
+    @Override
+    public Object getPathForItem(Cursor item) {
+        return null;
+    }
+}
diff --git a/src/com/android/photos/data/SQLiteContentProvider.java b/src/com/android/photos/data/SQLiteContentProvider.java
new file mode 100644
index 0000000..daffa6e
--- /dev/null
+++ b/src/com/android/photos/data/SQLiteContentProvider.java
@@ -0,0 +1,265 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.OperationApplicationException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * General purpose {@link ContentProvider} base class that uses SQLiteDatabase
+ * for storage.
+ */
+public abstract class SQLiteContentProvider extends ContentProvider {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "SQLiteContentProvider";
+
+    private SQLiteOpenHelper mOpenHelper;
+    private Set<Uri> mChangedUris;
+
+    private final ThreadLocal<Boolean> mApplyingBatch = new ThreadLocal<Boolean>();
+    private static final int SLEEP_AFTER_YIELD_DELAY = 4000;
+
+    /**
+     * Maximum number of operations allowed in a batch between yield points.
+     */
+    private static final int MAX_OPERATIONS_PER_YIELD_POINT = 500;
+
+    @Override
+    public boolean onCreate() {
+        Context context = getContext();
+        mOpenHelper = getDatabaseHelper(context);
+        mChangedUris = new HashSet<Uri>();
+        return true;
+    }
+
+    @Override
+    public void shutdown() {
+        getDatabaseHelper().close();
+    }
+
+    /**
+     * Returns a {@link SQLiteOpenHelper} that can open the database.
+     */
+    public abstract SQLiteOpenHelper getDatabaseHelper(Context context);
+
+    /**
+     * The equivalent of the {@link #insert} method, but invoked within a
+     * transaction.
+     */
+    public abstract Uri insertInTransaction(Uri uri, ContentValues values,
+            boolean callerIsSyncAdapter);
+
+    /**
+     * The equivalent of the {@link #update} method, but invoked within a
+     * transaction.
+     */
+    public abstract int updateInTransaction(Uri uri, ContentValues values, String selection,
+            String[] selectionArgs, boolean callerIsSyncAdapter);
+
+    /**
+     * The equivalent of the {@link #delete} method, but invoked within a
+     * transaction.
+     */
+    public abstract int deleteInTransaction(Uri uri, String selection, String[] selectionArgs,
+            boolean callerIsSyncAdapter);
+
+    /**
+     * Call this to add a URI to the list of URIs to be notified when the
+     * transaction is committed.
+     */
+    protected void postNotifyUri(Uri uri) {
+        synchronized (mChangedUris) {
+            mChangedUris.add(uri);
+        }
+    }
+
+    public boolean isCallerSyncAdapter(Uri uri) {
+        return false;
+    }
+
+    public SQLiteOpenHelper getDatabaseHelper() {
+        return mOpenHelper;
+    }
+
+    private boolean applyingBatch() {
+        return mApplyingBatch.get() != null && mApplyingBatch.get();
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        Uri result = null;
+        boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+        boolean applyingBatch = applyingBatch();
+        if (!applyingBatch) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                result = insertInTransaction(uri, values, callerIsSyncAdapter);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+
+            onEndTransaction(callerIsSyncAdapter);
+        } else {
+            result = insertInTransaction(uri, values, callerIsSyncAdapter);
+        }
+        return result;
+    }
+
+    @Override
+    public int bulkInsert(Uri uri, ContentValues[] values) {
+        int numValues = values.length;
+        boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            for (int i = 0; i < numValues; i++) {
+                @SuppressWarnings("unused")
+                Uri result = insertInTransaction(uri, values[i], callerIsSyncAdapter);
+                db.yieldIfContendedSafely();
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+
+        onEndTransaction(callerIsSyncAdapter);
+        return numValues;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        int count = 0;
+        boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+        boolean applyingBatch = applyingBatch();
+        if (!applyingBatch) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                count = updateInTransaction(uri, values, selection, selectionArgs,
+                        callerIsSyncAdapter);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+
+            onEndTransaction(callerIsSyncAdapter);
+        } else {
+            count = updateInTransaction(uri, values, selection, selectionArgs, callerIsSyncAdapter);
+        }
+
+        return count;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        int count = 0;
+        boolean callerIsSyncAdapter = isCallerSyncAdapter(uri);
+        boolean applyingBatch = applyingBatch();
+        if (!applyingBatch) {
+            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+
+            onEndTransaction(callerIsSyncAdapter);
+        } else {
+            count = deleteInTransaction(uri, selection, selectionArgs, callerIsSyncAdapter);
+        }
+        return count;
+    }
+
+    @Override
+    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
+            throws OperationApplicationException {
+        int ypCount = 0;
+        int opCount = 0;
+        boolean callerIsSyncAdapter = false;
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            mApplyingBatch.set(true);
+            final int numOperations = operations.size();
+            final ContentProviderResult[] results = new ContentProviderResult[numOperations];
+            for (int i = 0; i < numOperations; i++) {
+                if (++opCount >= MAX_OPERATIONS_PER_YIELD_POINT) {
+                    throw new OperationApplicationException(
+                            "Too many content provider operations between yield points. "
+                                    + "The maximum number of operations per yield point is "
+                                    + MAX_OPERATIONS_PER_YIELD_POINT, ypCount);
+                }
+                final ContentProviderOperation operation = operations.get(i);
+                if (!callerIsSyncAdapter && isCallerSyncAdapter(operation.getUri())) {
+                    callerIsSyncAdapter = true;
+                }
+                if (i > 0 && operation.isYieldAllowed()) {
+                    opCount = 0;
+                    if (db.yieldIfContendedSafely(SLEEP_AFTER_YIELD_DELAY)) {
+                        ypCount++;
+                    }
+                }
+                results[i] = operation.apply(this, results, i);
+            }
+            db.setTransactionSuccessful();
+            return results;
+        } finally {
+            mApplyingBatch.set(false);
+            db.endTransaction();
+            onEndTransaction(callerIsSyncAdapter);
+        }
+    }
+
+    protected Set<Uri> onEndTransaction(boolean callerIsSyncAdapter) {
+        Set<Uri> changed;
+        synchronized (mChangedUris) {
+            changed = new HashSet<Uri>(mChangedUris);
+            mChangedUris.clear();
+        }
+        ContentResolver resolver = getContext().getContentResolver();
+        for (Uri uri : changed) {
+            boolean syncToNetwork = !callerIsSyncAdapter && syncToNetwork(uri);
+            notifyChange(resolver, uri, syncToNetwork);
+        }
+        return changed;
+    }
+
+    protected void notifyChange(ContentResolver resolver, Uri uri, boolean syncToNetwork) {
+        resolver.notifyChange(uri, null, syncToNetwork);
+    }
+
+    protected boolean syncToNetwork(Uri uri) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java
new file mode 100644
index 0000000..1ef9e9f
--- /dev/null
+++ b/src/com/android/photos/data/SparseArrayBitmapPool.java
@@ -0,0 +1,148 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.util.SparseArray;
+
+import android.util.Pools.Pool;
+
+public class SparseArrayBitmapPool {
+
+    private static final int BITMAPS_TO_KEEP_AFTER_UNNEEDED_HINT = 4;
+    private int mCapacityBytes;
+    private SparseArray<Node> mStore = new SparseArray<Node>();
+    private int mSizeBytes = 0;
+
+    private Pool<Node> mNodePool;
+    private Node mPoolNodesHead = null;
+    private Node mPoolNodesTail = null;
+
+    protected static class Node {
+        Bitmap bitmap;
+        Node prevInBucket;
+        Node nextInBucket;
+        Node nextInPool;
+        Node prevInPool;
+    }
+
+    public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) {
+        mCapacityBytes = capacityBytes;
+        mNodePool = nodePool;
+    }
+
+    public synchronized void setCapacity(int capacityBytes) {
+        mCapacityBytes = capacityBytes;
+        freeUpCapacity(0);
+    }
+
+    private void freeUpCapacity(int bytesNeeded) {
+        int targetSize = mCapacityBytes - bytesNeeded;
+        while (mPoolNodesTail != null && mSizeBytes > targetSize) {
+            unlinkAndRecycleNode(mPoolNodesTail, true);
+        }
+    }
+
+    private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) {
+        // Remove the node from its spot in its bucket
+        if (n.prevInBucket != null) {
+            n.prevInBucket.nextInBucket = n.nextInBucket;
+        } else {
+            mStore.put(n.bitmap.getWidth(), n.nextInBucket);
+        }
+        if (n.nextInBucket != null) {
+            n.nextInBucket.prevInBucket = n.prevInBucket;
+        }
+
+        // Remove the node from its spot in the list of pool nodes
+        if (n.prevInPool != null) {
+            n.prevInPool.nextInPool = n.nextInPool;
+        } else {
+            mPoolNodesHead = n.nextInPool;
+        }
+        if (n.nextInPool != null) {
+            n.nextInPool.prevInPool = n.prevInPool;
+        } else {
+            mPoolNodesTail = n.prevInPool;
+        }
+
+        // Recycle the node
+        n.nextInBucket = null;
+        n.nextInPool = null;
+        n.prevInBucket = null;
+        n.prevInPool = null;
+        mSizeBytes -= n.bitmap.getByteCount();
+        if (recycleBitmap) n.bitmap.recycle();
+        n.bitmap = null;
+        mNodePool.release(n);
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        return mSizeBytes;
+    }
+
+    public synchronized Bitmap get(int width, int height) {
+        Node cur = mStore.get(width);
+        while (cur != null) {
+            if (cur.bitmap.getHeight() == height) {
+                Bitmap b = cur.bitmap;
+                unlinkAndRecycleNode(cur, false);
+                return b;
+            }
+            cur = cur.nextInBucket;
+        }
+        return null;
+    }
+
+    public synchronized boolean put(Bitmap b) {
+        if (b == null) {
+            return false;
+        }
+        int bytes = b.getByteCount();
+        freeUpCapacity(bytes);
+        Node newNode = mNodePool.acquire();
+        if (newNode == null) {
+            newNode = new Node();
+        }
+        newNode.bitmap = b;
+        newNode.prevInBucket = null;
+        newNode.prevInPool = null;
+        newNode.nextInPool = mPoolNodesHead;
+        mPoolNodesHead = newNode;
+        int key = b.getWidth();
+        newNode.nextInBucket = mStore.get(key);
+        if (newNode.nextInBucket != null) {
+            newNode.nextInBucket.prevInBucket = newNode;
+        }
+        mStore.put(key, newNode);
+        if (newNode.nextInPool == null) {
+            mPoolNodesTail = newNode;
+        } else {
+            newNode.nextInPool.prevInPool = newNode;
+        }
+        mSizeBytes += bytes;
+        return true;
+    }
+
+    public synchronized void clear() {
+        freeUpCapacity(mCapacityBytes);
+    }
+}
diff --git a/src/com/android/photos/drawables/AutoThumbnailDrawable.java b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
new file mode 100644
index 0000000..b51b670
--- /dev/null
+++ b/src/com/android/photos/drawables/AutoThumbnailDrawable.java
@@ -0,0 +1,309 @@
+/*
+ * 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.photos.drawables;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import com.android.photos.data.GalleryBitmapPool;
+
+import java.io.InputStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public abstract class AutoThumbnailDrawable<T> extends Drawable {
+
+    private static final String TAG = "AutoThumbnailDrawable";
+
+    private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor();
+    private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance();
+    private static byte[] sTempStorage = new byte[64 * 1024];
+
+    // UI thread only
+    private Paint mPaint = new Paint();
+    private Matrix mDrawMatrix = new Matrix();
+
+    // Decoder thread only
+    private BitmapFactory.Options mOptions = new BitmapFactory.Options();
+
+    // Shared, guarded by mLock
+    private Object mLock = new Object();
+    private Bitmap mBitmap;
+    protected T mData;
+    private boolean mIsQueued;
+    private int mImageWidth, mImageHeight;
+    private Rect mBounds = new Rect();
+    private int mSampleSize = 1;
+
+    public AutoThumbnailDrawable() {
+        mPaint.setAntiAlias(true);
+        mPaint.setFilterBitmap(true);
+        mDrawMatrix.reset();
+        mOptions.inTempStorage = sTempStorage;
+    }
+
+    protected abstract byte[] getPreferredImageBytes(T data);
+    protected abstract InputStream getFallbackImageStream(T data);
+    protected abstract boolean dataChangedLocked(T data);
+
+    public void setImage(T data, int width, int height) {
+        if (!dataChangedLocked(data)) return;
+        synchronized (mLock) {
+            mImageWidth = width;
+            mImageHeight = height;
+            mData = data;
+            setBitmapLocked(null);
+            refreshSampleSizeLocked();
+        }
+        invalidateSelf();
+    }
+
+    private void setBitmapLocked(Bitmap b) {
+        if (b == mBitmap) {
+            return;
+        }
+        if (mBitmap != null) {
+            sBitmapPool.put(mBitmap);
+        }
+        mBitmap = b;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        synchronized (mLock) {
+            mBounds.set(bounds);
+            if (mBounds.isEmpty()) {
+                mBitmap = null;
+            } else {
+                refreshSampleSizeLocked();
+                updateDrawMatrixLocked();
+            }
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mBitmap != null) {
+            canvas.save();
+            canvas.clipRect(mBounds);
+            canvas.concat(mDrawMatrix);
+            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+            canvas.restore();
+        } else {
+            // TODO: Draw placeholder...?
+        }
+    }
+
+    private void updateDrawMatrixLocked() {
+        if (mBitmap == null || mBounds.isEmpty()) {
+            mDrawMatrix.reset();
+            return;
+        }
+
+        float scale;
+        float dx = 0, dy = 0;
+
+        int dwidth = mBitmap.getWidth();
+        int dheight = mBitmap.getHeight();
+        int vwidth = mBounds.width();
+        int vheight = mBounds.height();
+
+        // Calculates a matrix similar to ScaleType.CENTER_CROP
+        if (dwidth * vheight > vwidth * dheight) {
+            scale = (float) vheight / (float) dheight;
+            dx = (vwidth - dwidth * scale) * 0.5f;
+        } else {
+            scale = (float) vwidth / (float) dwidth;
+            dy = (vheight - dheight * scale) * 0.5f;
+        }
+        if (scale < .8f) {
+            Log.w(TAG, "sample size was too small! Overdrawing! " + scale + ", " + mSampleSize);
+        } else if (scale > 1.5f) {
+            Log.w(TAG, "Potential quality loss! " + scale + ", " + mSampleSize);
+        }
+
+        mDrawMatrix.setScale(scale, scale);
+        mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+    }
+
+    private int calculateSampleSizeLocked(int dwidth, int dheight) {
+        float scale;
+
+        int vwidth = mBounds.width();
+        int vheight = mBounds.height();
+
+        // Inverse of updateDrawMatrixLocked
+        if (dwidth * vheight > vwidth * dheight) {
+            scale = (float) dheight / (float) vheight;
+        } else {
+            scale = (float) dwidth / (float) vwidth;
+        }
+        int result = Math.round(scale);
+        return result > 0 ? result : 1;
+    }
+
+    private void refreshSampleSizeLocked() {
+        if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) {
+            return;
+        }
+
+        int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight);
+        if (sampleSize != mSampleSize || mBitmap == null) {
+            mSampleSize = sampleSize;
+            loadBitmapLocked();
+        }
+    }
+
+    private void loadBitmapLocked() {
+        if (!mIsQueued && !mBounds.isEmpty()) {
+            unscheduleSelf(mUpdateBitmap);
+            sThreadPool.execute(mLoadBitmap);
+            mIsQueued = true;
+        }
+    }
+
+    public float getAspectRatio() {
+        return (float) mImageWidth / (float) mImageHeight;
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return -1;
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return -1;
+    }
+
+    @Override
+    public int getOpacity() {
+        Bitmap bm = mBitmap;
+        return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        int oldAlpha = mPaint.getAlpha();
+        if (alpha != oldAlpha) {
+            mPaint.setAlpha(alpha);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+        invalidateSelf();
+    }
+
+    private final Runnable mLoadBitmap = new Runnable() {
+        @Override
+        public void run() {
+            T data;
+            synchronized (mLock) {
+                data = mData;
+            }
+            int preferredSampleSize = 1;
+            byte[] preferred = getPreferredImageBytes(data);
+            boolean hasPreferred = (preferred != null && preferred.length > 0);
+            if (hasPreferred) {
+                mOptions.inJustDecodeBounds = true;
+                BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+                mOptions.inJustDecodeBounds = false;
+            }
+            int sampleSize, width, height;
+            synchronized (mLock) {
+                if (dataChangedLocked(data)) {
+                    return;
+                }
+                width = mImageWidth;
+                height = mImageHeight;
+                if (hasPreferred) {
+                    preferredSampleSize = calculateSampleSizeLocked(
+                            mOptions.outWidth, mOptions.outHeight);
+                }
+                sampleSize = calculateSampleSizeLocked(width, height);
+                mIsQueued = false;
+            }
+            Bitmap b = null;
+            InputStream is = null;
+            try {
+                if (hasPreferred) {
+                    mOptions.inSampleSize = preferredSampleSize;
+                    mOptions.inBitmap = sBitmapPool.get(
+                            mOptions.outWidth / preferredSampleSize,
+                            mOptions.outHeight / preferredSampleSize);
+                    b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
+                    if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+                        sBitmapPool.put(mOptions.inBitmap);
+                        mOptions.inBitmap = null;
+                    }
+                }
+                if (b == null) {
+                    is = getFallbackImageStream(data);
+                    mOptions.inSampleSize = sampleSize;
+                    mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize);
+                    b = BitmapFactory.decodeStream(is, null, mOptions);
+                    if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
+                        sBitmapPool.put(mOptions.inBitmap);
+                        mOptions.inBitmap = null;
+                    }
+                }
+            } catch (Exception e) {
+                Log.d(TAG, "Failed to fetch bitmap", e);
+                return;
+            } finally {
+                try {
+                    if (is != null) {
+                        is.close();
+                    }
+                } catch (Exception e) {}
+                if (b != null) {
+                    synchronized (mLock) {
+                        if (!dataChangedLocked(data)) {
+                            setBitmapLocked(b);
+                            scheduleSelf(mUpdateBitmap, 0);
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    private final Runnable mUpdateBitmap = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (AutoThumbnailDrawable.this) {
+                updateDrawMatrixLocked();
+                invalidateSelf();
+            }
+        }
+    };
+
+}
diff --git a/src/com/android/photos/drawables/DataUriThumbnailDrawable.java b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
new file mode 100644
index 0000000..c83b0c8
--- /dev/null
+++ b/src/com/android/photos/drawables/DataUriThumbnailDrawable.java
@@ -0,0 +1,54 @@
+/*
+ * 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.photos.drawables;
+
+import android.media.ExifInterface;
+import android.text.TextUtils;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DataUriThumbnailDrawable extends AutoThumbnailDrawable<String> {
+
+    @Override
+    protected byte[] getPreferredImageBytes(String data) {
+        byte[] thumbnail = null;
+        try {
+            ExifInterface exif = new ExifInterface(data);
+            if (exif.hasThumbnail()) {
+                thumbnail = exif.getThumbnail();
+             }
+        } catch (IOException e) { }
+        return thumbnail;
+    }
+
+    @Override
+    protected InputStream getFallbackImageStream(String data) {
+        try {
+            return new FileInputStream(data);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    @Override
+    protected boolean dataChangedLocked(String data) {
+        return !TextUtils.equals(mData, data);
+    }
+}
diff --git a/src/com/android/photos/drawables/MtpThumbnailDrawable.java b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
new file mode 100644
index 0000000..e35e069
--- /dev/null
+++ b/src/com/android/photos/drawables/MtpThumbnailDrawable.java
@@ -0,0 +1,61 @@
+/*
+ * 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.photos.drawables;
+
+import android.mtp.MtpDevice;
+import android.mtp.MtpObjectInfo;
+
+import com.android.gallery3d.ingest.MtpDeviceIndex;
+
+import java.io.InputStream;
+
+public class MtpThumbnailDrawable extends AutoThumbnailDrawable<MtpObjectInfo> {
+    public void setImage(MtpObjectInfo data) {
+        if (data == null) {
+            setImage(null, 0, 0);
+        } else {
+            setImage(data, data.getImagePixWidth(), data.getImagePixHeight());
+        }
+    }
+
+    @Override
+    protected byte[] getPreferredImageBytes(MtpObjectInfo data) {
+        if (data == null) {
+            return null;
+        }
+        MtpDevice device = MtpDeviceIndex.getInstance().getDevice();
+        if (device != null) {
+            return device.getThumbnail(data.getObjectHandle());
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    protected InputStream getFallbackImageStream(MtpObjectInfo data) {
+        // No fallback
+        return null;
+    }
+
+    @Override
+    protected boolean dataChangedLocked(MtpObjectInfo data) {
+        // We only fetch the MtpObjectInfo once when creating
+        // the index so checking the reference is enough
+        return mData == data;
+    }
+
+}
diff --git a/src/com/android/photos/shims/BitmapJobDrawable.java b/src/com/android/photos/shims/BitmapJobDrawable.java
new file mode 100644
index 0000000..32dbc80
--- /dev/null
+++ b/src/com/android/photos/shims/BitmapJobDrawable.java
@@ -0,0 +1,180 @@
+/*
+ * 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.photos.shims;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.ui.BitmapLoader;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.ThreadPool;
+import com.android.photos.data.GalleryBitmapPool;
+
+
+public class BitmapJobDrawable extends Drawable implements Runnable {
+
+    private ThumbnailLoader mLoader;
+    private MediaItem mItem;
+    private Bitmap mBitmap;
+    private Paint mPaint = new Paint();
+    private Matrix mDrawMatrix = new Matrix();
+    private int mRotation = 0;
+
+    public BitmapJobDrawable() {
+    }
+
+    public void setMediaItem(MediaItem item) {
+        if (mItem == item) return;
+
+        if (mLoader != null) {
+            mLoader.cancelLoad();
+        }
+        mItem = item;
+        if (mBitmap != null) {
+            GalleryBitmapPool.getInstance().put(mBitmap);
+            mBitmap = null;
+        }
+        if (mItem != null) {
+            // TODO: Figure out why ThumbnailLoader doesn't like to be re-used
+            mLoader = new ThumbnailLoader(this);
+            mLoader.startLoad();
+            mRotation = mItem.getRotation();
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void run() {
+        Bitmap bitmap = mLoader.getBitmap();
+        if (bitmap != null) {
+            mBitmap = bitmap;
+            updateDrawMatrix();
+        }
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.onBoundsChange(bounds);
+        updateDrawMatrix();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        Rect bounds = getBounds();
+        if (mBitmap != null) {
+            canvas.save();
+            canvas.clipRect(bounds);
+            canvas.concat(mDrawMatrix);
+            canvas.rotate(mRotation, bounds.centerX(), bounds.centerY());
+            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+            canvas.restore();
+        } else {
+            mPaint.setColor(0xFFCCCCCC);
+            canvas.drawRect(bounds, mPaint);
+        }
+    }
+
+    private void updateDrawMatrix() {
+        Rect bounds = getBounds();
+        if (mBitmap == null || bounds.isEmpty()) {
+            mDrawMatrix.reset();
+            return;
+        }
+
+        float scale;
+        float dx = 0, dy = 0;
+
+        int dwidth = mBitmap.getWidth();
+        int dheight = mBitmap.getHeight();
+        int vwidth = bounds.width();
+        int vheight = bounds.height();
+
+        // Calculates a matrix similar to ScaleType.CENTER_CROP
+        if (dwidth * vheight > vwidth * dheight) {
+            scale = (float) vheight / (float) dheight;
+            dx = (vwidth - dwidth * scale) * 0.5f;
+        } else {
+            scale = (float) vwidth / (float) dwidth;
+            dy = (vheight - dheight * scale) * 0.5f;
+        }
+
+        mDrawMatrix.setScale(scale, scale);
+        mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
+        invalidateSelf();
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+    }
+
+    @Override
+    public int getOpacity() {
+        Bitmap bm = mBitmap;
+        return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
+                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        int oldAlpha = mPaint.getAlpha();
+        if (alpha != oldAlpha) {
+            mPaint.setAlpha(alpha);
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mPaint.setColorFilter(cf);
+        invalidateSelf();
+    }
+
+    private static class ThumbnailLoader extends BitmapLoader {
+        private static final ThreadPool sThreadPool = new ThreadPool(0, 2);
+        private BitmapJobDrawable mParent;
+
+        public ThumbnailLoader(BitmapJobDrawable parent) {
+            mParent = parent;
+        }
+
+        @Override
+        protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
+            return sThreadPool.submit(
+                    mParent.mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
+        }
+
+        @Override
+        protected void onLoadComplete(Bitmap bitmap) {
+            mParent.scheduleSelf(mParent, 0);
+        }
+    }
+
+}
diff --git a/src/com/android/photos/shims/LoaderCompatShim.java b/src/com/android/photos/shims/LoaderCompatShim.java
new file mode 100644
index 0000000..d5bf710
--- /dev/null
+++ b/src/com/android/photos/shims/LoaderCompatShim.java
@@ -0,0 +1,31 @@
+/*
+ * 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.photos.shims;
+
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import java.util.ArrayList;
+
+
+public interface LoaderCompatShim<T> {
+    Drawable drawableForItem(T item, Drawable recycle);
+    Uri uriForItem(T item);
+    ArrayList<Uri> urisForSubItems(T item);
+    void deleteItemWithPath(Object path);
+    Object getPathForItem(T item);
+}
diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java
new file mode 100644
index 0000000..6142355
--- /dev/null
+++ b/src/com/android/photos/shims/MediaItemsLoader.java
@@ -0,0 +1,190 @@
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore.Files.FileColumns;
+import android.util.SparseArray;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.ItemConsumer;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.PhotoSetLoader;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear
+ * like a PhotoSetLoader
+ */
+public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor> {
+
+    private static final SyncListener sNullListener = new SyncListener() {
+        @Override
+        public void onSyncDone(MediaSet mediaSet, int resultCode) {
+        }
+    };
+
+    private final MediaSet mMediaSet;
+    private final DataManager mDataManager;
+    private Future<Integer> mSyncTask = null;
+    private ContentListener mObserver = new ContentListener() {
+        @Override
+        public void onContentDirty() {
+            onContentChanged();
+        }
+    };
+    private SparseArray<MediaItem> mMediaItems;
+
+    public MediaItemsLoader(Context context) {
+        super(context);
+        mDataManager = DataManager.from(context);
+        String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
+        mMediaSet = mDataManager.getMediaSet(path);
+    }
+
+    public MediaItemsLoader(Context context, String parentPath) {
+        super(context);
+        mDataManager = DataManager.from(getContext());
+        mMediaSet = mDataManager.getMediaSet(parentPath);
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        mMediaSet.addContentListener(mObserver);
+        mSyncTask = mMediaSet.requestSync(sNullListener);
+        forceLoad();
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        if (mSyncTask != null) {
+            mSyncTask.cancel();
+            mSyncTask = null;
+        }
+        return super.onCancelLoad();
+    }
+
+    @Override
+    protected void onStopLoading() {
+        super.onStopLoading();
+        cancelLoad();
+        mMediaSet.removeContentListener(mObserver);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        onStopLoading();
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // TODO: This probably doesn't work
+        mMediaSet.reload();
+        final MatrixCursor cursor = new MatrixCursor(PhotoSetLoader.PROJECTION);
+        final Object[] row = new Object[PhotoSetLoader.PROJECTION.length];
+        final SparseArray<MediaItem> mediaItems = new SparseArray<MediaItem>();
+        mMediaSet.enumerateTotalMediaItems(new ItemConsumer() {
+            @Override
+            public void consume(int index, MediaItem item) {
+                row[PhotoSetLoader.INDEX_ID] = index;
+                row[PhotoSetLoader.INDEX_DATA] = item.getContentUri().toString();
+                row[PhotoSetLoader.INDEX_DATE_ADDED] = item.getDateInMs();
+                row[PhotoSetLoader.INDEX_HEIGHT] = item.getHeight();
+                row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+                row[PhotoSetLoader.INDEX_WIDTH] = item.getWidth();
+                int rawMediaType = item.getMediaType();
+                int mappedMediaType = FileColumns.MEDIA_TYPE_NONE;
+                if (rawMediaType == MediaItem.MEDIA_TYPE_IMAGE) {
+                    mappedMediaType = FileColumns.MEDIA_TYPE_IMAGE;
+                } else if (rawMediaType == MediaItem.MEDIA_TYPE_VIDEO) {
+                    mappedMediaType = FileColumns.MEDIA_TYPE_VIDEO;
+                }
+                row[PhotoSetLoader.INDEX_MEDIA_TYPE] = mappedMediaType;
+                row[PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS] =
+                        item.getSupportedOperations();
+                cursor.addRow(row);
+                mediaItems.append(index, item);
+            }
+        });
+        synchronized (mMediaSet) {
+            mMediaItems = mediaItems;
+        }
+        return cursor;
+    }
+
+    @Override
+    public Drawable drawableForItem(Cursor item, Drawable recycle) {
+        BitmapJobDrawable drawable = null;
+        if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+            drawable = new BitmapJobDrawable();
+        } else {
+            drawable = (BitmapJobDrawable) recycle;
+        }
+        int index = item.getInt(PhotoSetLoader.INDEX_ID);
+        drawable.setMediaItem(mMediaItems.get(index));
+        return drawable;
+    }
+
+    public static int getThumbnailSize() {
+        return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+    }
+
+    @Override
+    public Uri uriForItem(Cursor item) {
+        int index = item.getInt(PhotoSetLoader.INDEX_ID);
+        MediaItem mi = mMediaItems.get(index);
+        return mi == null ? null : mi.getContentUri();
+    }
+
+    @Override
+    public ArrayList<Uri> urisForSubItems(Cursor item) {
+        return null;
+    }
+
+    @Override
+    public void deleteItemWithPath(Object path) {
+        MediaObject o = mDataManager.getMediaObject((Path) path);
+        if (o != null) {
+            o.delete();
+        }
+    }
+
+    @Override
+    public Object getPathForItem(Cursor item) {
+        int index = item.getInt(PhotoSetLoader.INDEX_ID);
+        MediaItem mi = mMediaItems.get(index);
+        if (mi != null) {
+            return mi.getPath();
+        }
+        return null;
+    }
+
+}
diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java
new file mode 100644
index 0000000..9093bc1
--- /dev/null
+++ b/src/com/android/photos/shims/MediaSetLoader.java
@@ -0,0 +1,191 @@
+/*
+ * 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.photos.shims;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import com.android.gallery3d.data.ContentListener;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.data.MediaSet.SyncListener;
+import com.android.gallery3d.util.Future;
+import com.android.photos.data.AlbumSetLoader;
+
+import java.util.ArrayList;
+
+/**
+ * Returns all MediaSets in a MediaSet, wrapping them in a cursor to appear
+ * like a AlbumSetLoader.
+ */
+public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCompatShim<Cursor>{
+
+    private static final SyncListener sNullListener = new SyncListener() {
+        @Override
+        public void onSyncDone(MediaSet mediaSet, int resultCode) {
+        }
+    };
+
+    private final MediaSet mMediaSet;
+    private final DataManager mDataManager;
+    private Future<Integer> mSyncTask = null;
+    private ContentListener mObserver = new ContentListener() {
+        @Override
+        public void onContentDirty() {
+            onContentChanged();
+        }
+    };
+
+    private ArrayList<MediaItem> mCoverItems;
+
+    public MediaSetLoader(Context context) {
+        super(context);
+        mDataManager = DataManager.from(context);
+        String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL);
+        mMediaSet = mDataManager.getMediaSet(path);
+    }
+
+    public MediaSetLoader(Context context, String path) {
+        super(context);
+        mDataManager = DataManager.from(getContext());
+        mMediaSet = mDataManager.getMediaSet(path);
+    }
+
+    @Override
+    protected void onStartLoading() {
+        super.onStartLoading();
+        mMediaSet.addContentListener(mObserver);
+        mSyncTask = mMediaSet.requestSync(sNullListener);
+        forceLoad();
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        if (mSyncTask != null) {
+            mSyncTask.cancel();
+            mSyncTask = null;
+        }
+        return super.onCancelLoad();
+    }
+
+    @Override
+    protected void onStopLoading() {
+        super.onStopLoading();
+        cancelLoad();
+        mMediaSet.removeContentListener(mObserver);
+    }
+
+    @Override
+    protected void onReset() {
+        super.onReset();
+        onStopLoading();
+    }
+
+    @Override
+    public Cursor loadInBackground() {
+        // TODO: This probably doesn't work
+        mMediaSet.reload();
+        final MatrixCursor cursor = new MatrixCursor(AlbumSetLoader.PROJECTION);
+        final Object[] row = new Object[AlbumSetLoader.PROJECTION.length];
+        int count = mMediaSet.getSubMediaSetCount();
+        ArrayList<MediaItem> coverItems = new ArrayList<MediaItem>(count);
+        for (int i = 0; i < count; i++) {
+            MediaSet m = mMediaSet.getSubMediaSet(i);
+            m.reload();
+            row[AlbumSetLoader.INDEX_ID] = i;
+            row[AlbumSetLoader.INDEX_TITLE] = m.getName();
+            row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount();
+            row[AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS] = m.getSupportedOperations();
+            MediaItem coverItem = m.getCoverMediaItem();
+            if (coverItem != null) {
+                row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs();
+            }
+            coverItems.add(coverItem);
+            cursor.addRow(row);
+        }
+        synchronized (mMediaSet) {
+            mCoverItems = coverItems;
+        }
+        return cursor;
+    }
+
+    @Override
+    public Drawable drawableForItem(Cursor item, Drawable recycle) {
+        BitmapJobDrawable drawable = null;
+        if (recycle == null || !(recycle instanceof BitmapJobDrawable)) {
+            drawable = new BitmapJobDrawable();
+        } else {
+            drawable = (BitmapJobDrawable) recycle;
+        }
+        int index = item.getInt(AlbumSetLoader.INDEX_ID);
+        drawable.setMediaItem(mCoverItems.get(index));
+        return drawable;
+    }
+
+    public static int getThumbnailSize() {
+        return MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL);
+    }
+
+    @Override
+    public Uri uriForItem(Cursor item) {
+        int index = item.getInt(AlbumSetLoader.INDEX_ID);
+        MediaSet ms = mMediaSet.getSubMediaSet(index);
+        return ms == null ? null : ms.getContentUri();
+    }
+
+    @Override
+    public ArrayList<Uri> urisForSubItems(Cursor item) {
+        int index = item.getInt(AlbumSetLoader.INDEX_ID);
+        MediaSet ms = mMediaSet.getSubMediaSet(index);
+        if (ms == null) return null;
+        final ArrayList<Uri> result = new ArrayList<Uri>();
+        ms.enumerateMediaItems(new MediaSet.ItemConsumer() {
+            @Override
+            public void consume(int index, MediaItem item) {
+                if (item != null) {
+                    result.add(item.getContentUri());
+                }
+            }
+        });
+        return result;
+    }
+
+    @Override
+    public void deleteItemWithPath(Object path) {
+        MediaObject o = mDataManager.getMediaObject((Path) path);
+        if (o != null) {
+            o.delete();
+        }
+    }
+
+    @Override
+    public Object getPathForItem(Cursor item) {
+        int index = item.getInt(AlbumSetLoader.INDEX_ID);
+        MediaSet ms = mMediaSet.getSubMediaSet(index);
+        if (ms != null) {
+            return ms.getPath();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
new file mode 100644
index 0000000..c38f8f7
--- /dev/null
+++ b/src/com/android/photos/views/BlockingGLTextureView.java
@@ -0,0 +1,427 @@
+/*
+ * 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.photos.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLSurfaceView.Renderer;
+import android.opengl.GLUtils;
+import android.util.Log;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+import javax.microedition.khronos.opengles.GL10;
+
+
+public class BlockingGLTextureView extends TextureView
+        implements SurfaceTextureListener {
+
+    private RenderThread mRenderThread;
+
+    public BlockingGLTextureView(Context context) {
+        super(context);
+        setSurfaceTextureListener(this);
+    }
+
+    public void setRenderer(Renderer renderer) {
+        if (mRenderThread != null) {
+            throw new IllegalArgumentException("Renderer already set");
+        }
+        mRenderThread = new RenderThread(renderer);
+    }
+
+    public void render() {
+        mRenderThread.render();
+    }
+
+    public void destroy() {
+        if (mRenderThread != null) {
+            mRenderThread.finish();
+            mRenderThread = null;
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width,
+            int height) {
+        mRenderThread.setSurface(surface);
+        mRenderThread.setSize(width, height);
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width,
+            int height) {
+        mRenderThread.setSize(width, height);
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        if (mRenderThread != null) {
+            mRenderThread.setSurface(null);
+        }
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            destroy();
+        } catch (Throwable t) {}
+        super.finalize();
+    }
+
+    /**
+     * An EGL helper class.
+     */
+
+    private static class EglHelper {
+        private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+        private static final int EGL_OPENGL_ES2_BIT = 4;
+
+        EGL10 mEgl;
+        EGLDisplay mEglDisplay;
+        EGLSurface mEglSurface;
+        EGLConfig mEglConfig;
+        EGLContext mEglContext;
+
+        private EGLConfig chooseEglConfig() {
+            int[] configsCount = new int[1];
+            EGLConfig[] configs = new EGLConfig[1];
+            int[] configSpec = getConfig();
+            if (!mEgl.eglChooseConfig(mEglDisplay, configSpec, configs, 1, configsCount)) {
+                throw new IllegalArgumentException("eglChooseConfig failed " +
+                        GLUtils.getEGLErrorString(mEgl.eglGetError()));
+            } else if (configsCount[0] > 0) {
+                return configs[0];
+            }
+            return null;
+        }
+
+        private static int[] getConfig() {
+            return new int[] {
+                    EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                    EGL10.EGL_RED_SIZE, 8,
+                    EGL10.EGL_GREEN_SIZE, 8,
+                    EGL10.EGL_BLUE_SIZE, 8,
+                    EGL10.EGL_ALPHA_SIZE, 8,
+                    EGL10.EGL_DEPTH_SIZE, 0,
+                    EGL10.EGL_STENCIL_SIZE, 0,
+                    EGL10.EGL_NONE
+            };
+        }
+
+        EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
+            int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+            return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list);
+        }
+
+        /**
+         * Initialize EGL for a given configuration spec.
+         */
+        public void start() {
+            /*
+             * Get an EGL instance
+             */
+            mEgl = (EGL10) EGLContext.getEGL();
+
+            /*
+             * Get to the default display.
+             */
+            mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+
+            if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("eglGetDisplay failed");
+            }
+
+            /*
+             * We can now initialize EGL for that display
+             */
+            int[] version = new int[2];
+            if(!mEgl.eglInitialize(mEglDisplay, version)) {
+                throw new RuntimeException("eglInitialize failed");
+            }
+            mEglConfig = chooseEglConfig();
+
+            /*
+            * Create an EGL context. We want to do this as rarely as we can, because an
+            * EGL context is a somewhat heavy object.
+            */
+            mEglContext = createContext(mEgl, mEglDisplay, mEglConfig);
+
+            if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
+                mEglContext = null;
+                throwEglException("createContext");
+            }
+
+            mEglSurface = null;
+        }
+
+        /**
+         * Create an egl surface for the current SurfaceTexture surface. If a surface
+         * already exists, destroy it before creating the new surface.
+         *
+         * @return true if the surface was created successfully.
+         */
+        public boolean createSurface(SurfaceTexture surface) {
+            /*
+             * Check preconditions.
+             */
+            if (mEgl == null) {
+                throw new RuntimeException("egl not initialized");
+            }
+            if (mEglDisplay == null) {
+                throw new RuntimeException("eglDisplay not initialized");
+            }
+            if (mEglConfig == null) {
+                throw new RuntimeException("mEglConfig not initialized");
+            }
+
+            /*
+             *  The window size has changed, so we need to create a new
+             *  surface.
+             */
+            destroySurfaceImp();
+
+            /*
+             * Create an EGL surface we can render into.
+             */
+            if (surface != null) {
+                mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surface, null);
+            } else {
+                mEglSurface = null;
+            }
+
+            if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
+                int error = mEgl.eglGetError();
+                if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
+                    Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
+                }
+                return false;
+            }
+
+            /*
+             * Before we can issue GL commands, we need to make sure
+             * the context is current and bound to a surface.
+             */
+            if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                /*
+                 * Could not make the context current, probably because the underlying
+                 * SurfaceView surface has been destroyed.
+                 */
+                logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
+                return false;
+            }
+
+            return true;
+        }
+
+        /**
+         * Create a GL object for the current EGL context.
+         */
+        public GL10 createGL() {
+            return (GL10) mEglContext.getGL();
+        }
+
+        /**
+         * Display the current render surface.
+         * @return the EGL error code from eglSwapBuffers.
+         */
+        public int swap() {
+            if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+                return mEgl.eglGetError();
+            }
+            return EGL10.EGL_SUCCESS;
+        }
+
+        public void destroySurface() {
+            destroySurfaceImp();
+        }
+
+        private void destroySurfaceImp() {
+            if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
+                mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_SURFACE,
+                        EGL10.EGL_NO_CONTEXT);
+                mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
+                mEglSurface = null;
+            }
+        }
+
+        public void finish() {
+            if (mEglContext != null) {
+                mEgl.eglDestroyContext(mEglDisplay, mEglContext);
+                mEglContext = null;
+            }
+            if (mEglDisplay != null) {
+                mEgl.eglTerminate(mEglDisplay);
+                mEglDisplay = null;
+            }
+        }
+
+        private void throwEglException(String function) {
+            throwEglException(function, mEgl.eglGetError());
+        }
+
+        public static void throwEglException(String function, int error) {
+            String message = formatEglError(function, error);
+            throw new RuntimeException(message);
+        }
+
+        public static void logEglErrorAsWarning(String tag, String function, int error) {
+            Log.w(tag, formatEglError(function, error));
+        }
+
+        public static String formatEglError(String function, int error) {
+            return function + " failed: " + error;
+        }
+
+    }
+
+    private static class RenderThread extends Thread {
+        private static final int INVALID = -1;
+        private static final int RENDER = 1;
+        private static final int CHANGE_SURFACE = 2;
+        private static final int RESIZE_SURFACE = 3;
+        private static final int FINISH = 4;
+
+        private EglHelper mEglHelper = new EglHelper();
+
+        private Object mLock = new Object();
+        private int mExecMsgId = INVALID;
+        private SurfaceTexture mSurface;
+        private Renderer mRenderer;
+        private int mWidth, mHeight;
+
+        private boolean mFinished = false;
+        private GL10 mGL;
+
+        public RenderThread(Renderer renderer) {
+            super("RenderThread");
+            mRenderer = renderer;
+            start();
+        }
+
+        private void checkRenderer() {
+            if (mRenderer == null) {
+                throw new IllegalArgumentException("Renderer is null!");
+            }
+        }
+
+        private void checkSurface() {
+            if (mSurface == null) {
+                throw new IllegalArgumentException("surface is null!");
+            }
+        }
+
+        public void setSurface(SurfaceTexture surface) {
+            // If the surface is null we're being torn down, don't need a
+            // renderer then
+            if (surface != null) {
+                checkRenderer();
+            }
+            mSurface = surface;
+            exec(CHANGE_SURFACE);
+        }
+
+        public void setSize(int width, int height) {
+            checkRenderer();
+            checkSurface();
+            mWidth = width;
+            mHeight = height;
+            exec(RESIZE_SURFACE);
+        }
+
+        public void render() {
+            checkRenderer();
+            if (mSurface != null) {
+                exec(RENDER);
+                mSurface.updateTexImage();
+            }
+        }
+
+        public void finish() {
+            mSurface = null;
+            exec(FINISH);
+            try {
+                join();
+            } catch (InterruptedException e) {}
+        }
+
+        private void exec(int msgid) {
+            synchronized (mLock) {
+                if (mExecMsgId != INVALID) {
+                    throw new IllegalArgumentException("Message already set - multithreaded access?");
+                }
+                mExecMsgId = msgid;
+                mLock.notify();
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {}
+            }
+        }
+
+        private void handleMessageLocked(int what) {
+            switch (what) {
+            case CHANGE_SURFACE:
+                if (mEglHelper.createSurface(mSurface)) {
+                    mGL = mEglHelper.createGL();
+                    mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig);
+                }
+                break;
+            case RESIZE_SURFACE:
+                mRenderer.onSurfaceChanged(mGL, mWidth, mHeight);
+                break;
+            case RENDER:
+                mRenderer.onDrawFrame(mGL);
+                mEglHelper.swap();
+                break;
+            case FINISH:
+                mEglHelper.destroySurface();
+                mEglHelper.finish();
+                mFinished = true;
+                break;
+            }
+        }
+
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                mEglHelper.start();
+                while (!mFinished) {
+                    while (mExecMsgId == INVALID) {
+                        try {
+                            mLock.wait();
+                        } catch (InterruptedException e) {}
+                    }
+                    handleMessageLocked(mExecMsgId);
+                    mExecMsgId = INVALID;
+                    mLock.notify();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/photos/views/GalleryThumbnailView.java b/src/com/android/photos/views/GalleryThumbnailView.java
new file mode 100644
index 0000000..e5dd6f2
--- /dev/null
+++ b/src/com/android/photos/views/GalleryThumbnailView.java
@@ -0,0 +1,883 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.DataSetObserver;
+import android.graphics.Canvas;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.VelocityTrackerCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.EdgeEffectCompat;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.ListAdapter;
+import android.widget.OverScroller;
+
+import java.util.ArrayList;
+
+public class GalleryThumbnailView extends ViewGroup {
+
+    public interface GalleryThumbnailAdapter extends ListAdapter {
+        /**
+         * @param position Position to get the intrinsic aspect ratio for
+         * @return width / height
+         */
+        float getIntrinsicAspectRatio(int position);
+    }
+
+    private static final String TAG = "GalleryThumbnailView";
+    private static final float ASPECT_RATIO = (float) Math.sqrt(1.5f);
+    private static final int LAND_UNITS = 2;
+    private static final int PORT_UNITS = 3;
+
+    private GalleryThumbnailAdapter mAdapter;
+
+    private final RecycleBin mRecycler = new RecycleBin();
+
+    private final AdapterDataSetObserver mObserver = new AdapterDataSetObserver();
+
+    private boolean mDataChanged;
+    private int mOldItemCount;
+    private int mItemCount;
+    private boolean mHasStableIds;
+
+    private int mFirstPosition;
+
+    private boolean mPopulating;
+    private boolean mInLayout;
+
+    private int mTouchSlop;
+    private int mMaximumVelocity;
+    private int mFlingVelocity;
+    private float mLastTouchX;
+    private float mTouchRemainderX;
+    private int mActivePointerId;
+
+    private static final int TOUCH_MODE_IDLE = 0;
+    private static final int TOUCH_MODE_DRAGGING = 1;
+    private static final int TOUCH_MODE_FLINGING = 2;
+
+    private int mTouchMode;
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+    private final OverScroller mScroller;
+
+    private final EdgeEffectCompat mLeftEdge;
+    private final EdgeEffectCompat mRightEdge;
+
+    private int mLargeColumnWidth;
+    private int mSmallColumnWidth;
+    private int mLargeColumnUnitCount = 8;
+    private int mSmallColumnUnitCount = 10;
+
+    public GalleryThumbnailView(Context context) {
+        this(context, null);
+    }
+
+    public GalleryThumbnailView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public GalleryThumbnailView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        final ViewConfiguration vc = ViewConfiguration.get(context);
+        mTouchSlop = vc.getScaledTouchSlop();
+        mMaximumVelocity = vc.getScaledMaximumFlingVelocity();
+        mFlingVelocity = vc.getScaledMinimumFlingVelocity();
+        mScroller = new OverScroller(context);
+
+        mLeftEdge = new EdgeEffectCompat(context);
+        mRightEdge = new EdgeEffectCompat(context);
+        setWillNotDraw(false);
+        setClipToPadding(false);
+    }
+
+    @Override
+    public void requestLayout() {
+        if (!mPopulating) {
+            super.requestLayout();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthMode != MeasureSpec.EXACTLY) {
+            Log.e(TAG, "onMeasure: must have an exact width or match_parent! " +
+                    "Using fallback spec of EXACTLY " + widthSize);
+        }
+        if (heightMode != MeasureSpec.EXACTLY) {
+            Log.e(TAG, "onMeasure: must have an exact height or match_parent! " +
+                    "Using fallback spec of EXACTLY " + heightSize);
+        }
+
+        setMeasuredDimension(widthSize, heightSize);
+
+        float portSpaces = mLargeColumnUnitCount / PORT_UNITS;
+        float height = getMeasuredHeight() / portSpaces;
+        mLargeColumnWidth = (int) (height / ASPECT_RATIO);
+        portSpaces++;
+        height = getMeasuredHeight() / portSpaces;
+        mSmallColumnWidth = (int) (height / ASPECT_RATIO);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        mInLayout = true;
+        populate();
+        mInLayout = false;
+
+        final int width = r - l;
+        final int height = b - t;
+        mLeftEdge.setSize(width, height);
+        mRightEdge.setSize(width, height);
+    }
+
+    private void populate() {
+        if (getWidth() == 0 || getHeight() == 0) {
+            return;
+        }
+
+        // TODO: Handle size changing
+//        final int colCount = mColCount;
+//        if (mItemTops == null || mItemTops.length != colCount) {
+//            mItemTops = new int[colCount];
+//            mItemBottoms = new int[colCount];
+//            final int top = getPaddingTop();
+//            final int offset = top + Math.min(mRestoreOffset, 0);
+//            Arrays.fill(mItemTops, offset);
+//            Arrays.fill(mItemBottoms, offset);
+//            mLayoutRecords.clear();
+//            if (mInLayout) {
+//                removeAllViewsInLayout();
+//            } else {
+//                removeAllViews();
+//            }
+//            mRestoreOffset = 0;
+//        }
+
+        mPopulating = true;
+        layoutChildren(mDataChanged);
+        fillRight(mFirstPosition + getChildCount(), 0);
+        fillLeft(mFirstPosition - 1, 0);
+        mPopulating = false;
+        mDataChanged = false;
+    }
+
+    final void layoutChildren(boolean queryAdapter) {
+// TODO
+//        final int childCount = getChildCount();
+//        for (int i = 0; i < childCount; i++) {
+//            View child = getChildAt(i);
+//
+//            if (child.isLayoutRequested()) {
+//                final int widthSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(), MeasureSpec.EXACTLY);
+//                final int heightSpec = MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(), MeasureSpec.EXACTLY);
+//                child.measure(widthSpec, heightSpec);
+//                child.layout(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+//            }
+//
+//            int childTop = mItemBottoms[col] > Integer.MIN_VALUE ?
+//                    mItemBottoms[col] + mItemMargin : child.getTop();
+//            if (span > 1) {
+//                int lowest = childTop;
+//                for (int j = col + 1; j < col + span; j++) {
+//                    final int bottom = mItemBottoms[j] + mItemMargin;
+//                    if (bottom > lowest) {
+//                        lowest = bottom;
+//                    }
+//                }
+//                childTop = lowest;
+//            }
+//            final int childHeight = child.getMeasuredHeight();
+//            final int childBottom = childTop + childHeight;
+//            final int childLeft = paddingLeft + col * (colWidth + itemMargin);
+//            final int childRight = childLeft + child.getMeasuredWidth();
+//            child.layout(childLeft, childTop, childRight, childBottom);
+//        }
+    }
+
+    /**
+     * Obtain the view and add it to our list of children. The view can be made
+     * fresh, converted from an unused view, or used as is if it was in the
+     * recycle bin.
+     *
+     * @param startPosition Logical position in the list to start from
+     * @param x Left or right edge of the view to add
+     * @param forward If true, align left edge to x and increase position.
+     *                If false, align right edge to x and decrease position.
+     * @return Number of views added
+     */
+    private int makeAndAddColumn(int startPosition, int x, boolean forward) {
+        int columnWidth = mLargeColumnWidth;
+        int addViews = 0;
+        for (int remaining = mLargeColumnUnitCount, i = 0;
+                remaining > 0 && startPosition + i >= 0 && startPosition + i < mItemCount;
+                i += forward ? 1 : -1, addViews++) {
+            if (mAdapter.getIntrinsicAspectRatio(startPosition + i) >= 1f) {
+                // landscape
+                remaining -= LAND_UNITS;
+            } else {
+                // portrait
+                remaining -= PORT_UNITS;
+                if (remaining < 0) {
+                    remaining += (mSmallColumnUnitCount - mLargeColumnUnitCount);
+                    columnWidth = mSmallColumnWidth;
+                }
+            }
+        }
+        int nextTop = 0;
+        for (int i = 0; i < addViews; i++) {
+            int position = startPosition + (forward ? i : -i);
+            View child = obtainView(position, null);
+            if (child.getParent() != this) {
+                if (mInLayout) {
+                    addViewInLayout(child, forward ? -1 : 0, child.getLayoutParams());
+                } else {
+                    addView(child, forward ? -1 : 0);
+                }
+            }
+            int heightSize = (int) (.5f + (mAdapter.getIntrinsicAspectRatio(position) >= 1f
+                    ? columnWidth / ASPECT_RATIO
+                    : columnWidth * ASPECT_RATIO));
+            int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
+            int widthSpec = MeasureSpec.makeMeasureSpec(columnWidth, MeasureSpec.EXACTLY);
+            child.measure(widthSpec, heightSpec);
+            int childLeft = forward ? x : x - columnWidth;
+            child.layout(childLeft, nextTop, childLeft + columnWidth, nextTop + heightSize);
+            nextTop += heightSize;
+        }
+        return addViews;
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        mVelocityTracker.addMovement(ev);
+        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mVelocityTracker.clear();
+                mScroller.abortAnimation();
+                mLastTouchX = ev.getX();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mTouchRemainderX = 0;
+                if (mTouchMode == TOUCH_MODE_FLINGING) {
+                    // Catch!
+                    mTouchMode = TOUCH_MODE_DRAGGING;
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (index < 0) {
+                    Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
+                            mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
+                            "event stream?");
+                    return false;
+                }
+                final float x = MotionEventCompat.getX(ev, index);
+                final float dx = x - mLastTouchX + mTouchRemainderX;
+                final int deltaY = (int) dx;
+                mTouchRemainderX = dx - deltaY;
+
+                if (Math.abs(dx) > mTouchSlop) {
+                    mTouchMode = TOUCH_MODE_DRAGGING;
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mVelocityTracker.addMovement(ev);
+        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mVelocityTracker.clear();
+                mScroller.abortAnimation();
+                mLastTouchX = ev.getX();
+                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+                mTouchRemainderX = 0;
+                break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+                if (index < 0) {
+                    Log.e(TAG, "onInterceptTouchEvent could not find pointer with id " +
+                            mActivePointerId + " - did StaggeredGridView receive an inconsistent " +
+                            "event stream?");
+                    return false;
+                }
+                final float x = MotionEventCompat.getX(ev, index);
+                final float dx = x - mLastTouchX + mTouchRemainderX;
+                final int deltaX = (int) dx;
+                mTouchRemainderX = dx - deltaX;
+
+                if (Math.abs(dx) > mTouchSlop) {
+                    mTouchMode = TOUCH_MODE_DRAGGING;
+                }
+
+                if (mTouchMode == TOUCH_MODE_DRAGGING) {
+                    mLastTouchX = x;
+
+                    if (!trackMotionScroll(deltaX, true)) {
+                        // Break fling velocity if we impacted an edge.
+                        mVelocityTracker.clear();
+                    }
+                }
+            } break;
+
+            case MotionEvent.ACTION_CANCEL:
+                mTouchMode = TOUCH_MODE_IDLE;
+                break;
+
+            case MotionEvent.ACTION_UP: {
+                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                final float velocity = VelocityTrackerCompat.getXVelocity(mVelocityTracker,
+                        mActivePointerId);
+                if (Math.abs(velocity) > mFlingVelocity) { // TODO
+                    mTouchMode = TOUCH_MODE_FLINGING;
+                    mScroller.fling(0, 0, (int) velocity, 0,
+                            Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
+                    mLastTouchX = 0;
+                    ViewCompat.postInvalidateOnAnimation(this);
+                } else {
+                    mTouchMode = TOUCH_MODE_IDLE;
+                }
+
+            } break;
+        }
+        return true;
+    }
+
+    /**
+     *
+     * @param deltaX Pixels that content should move by
+     * @return true if the movement completed, false if it was stopped prematurely.
+     */
+    private boolean trackMotionScroll(int deltaX, boolean allowOverScroll) {
+        final boolean contentFits = contentFits();
+        final int allowOverhang = Math.abs(deltaX);
+
+        final int overScrolledBy;
+        final int movedBy;
+        if (!contentFits) {
+            final int overhang;
+            final boolean up;
+            mPopulating = true;
+            if (deltaX > 0) {
+                overhang = fillLeft(mFirstPosition - 1, allowOverhang);
+                up = true;
+            } else {
+                overhang = fillRight(mFirstPosition + getChildCount(), allowOverhang);
+                up = false;
+            }
+            movedBy = Math.min(overhang, allowOverhang);
+            offsetChildren(up ? movedBy : -movedBy);
+            recycleOffscreenViews();
+            mPopulating = false;
+            overScrolledBy = allowOverhang - overhang;
+        } else {
+            overScrolledBy = allowOverhang;
+            movedBy = 0;
+        }
+
+        if (allowOverScroll) {
+            final int overScrollMode = ViewCompat.getOverScrollMode(this);
+
+            if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
+                    (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits)) {
+
+                if (overScrolledBy > 0) {
+                    EdgeEffectCompat edge = deltaX > 0 ? mLeftEdge : mRightEdge;
+                    edge.onPull((float) Math.abs(deltaX) / getWidth());
+                    ViewCompat.postInvalidateOnAnimation(this);
+                }
+            }
+        }
+
+        return deltaX == 0 || movedBy != 0;
+    }
+
+    /**
+     * Important: this method will leave offscreen views attached if they
+     * are required to maintain the invariant that child view with index i
+     * is always the view corresponding to position mFirstPosition + i.
+     */
+    private void recycleOffscreenViews() {
+        final int height = getHeight();
+        final int clearAbove = 0;
+        final int clearBelow = height;
+        for (int i = getChildCount() - 1; i >= 0; i--) {
+            final View child = getChildAt(i);
+            if (child.getTop() <= clearBelow)  {
+                // There may be other offscreen views, but we need to maintain
+                // the invariant documented above.
+                break;
+            }
+
+            if (mInLayout) {
+                removeViewsInLayout(i, 1);
+            } else {
+                removeViewAt(i);
+            }
+
+            mRecycler.addScrap(child);
+        }
+
+        while (getChildCount() > 0) {
+            final View child = getChildAt(0);
+            if (child.getBottom() >= clearAbove) {
+                // There may be other offscreen views, but we need to maintain
+                // the invariant documented above.
+                break;
+            }
+
+            if (mInLayout) {
+                removeViewsInLayout(0, 1);
+            } else {
+                removeViewAt(0);
+            }
+
+            mRecycler.addScrap(child);
+            mFirstPosition++;
+        }
+    }
+
+    final void offsetChildren(int offset) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            child.layout(child.getLeft() + offset, child.getTop(),
+                    child.getRight() + offset, child.getBottom());
+        }
+    }
+
+    private boolean contentFits() {
+        final int childCount = getChildCount();
+        if (childCount == 0) return true;
+        if (childCount != mItemCount) return false;
+
+        return getChildAt(0).getLeft() >= getPaddingLeft() &&
+                getChildAt(childCount - 1).getRight() <= getWidth() - getPaddingRight();
+    }
+
+    private void recycleAllViews() {
+        for (int i = 0; i < getChildCount(); i++) {
+            mRecycler.addScrap(getChildAt(i));
+        }
+
+        if (mInLayout) {
+            removeAllViewsInLayout();
+        } else {
+            removeAllViews();
+        }
+    }
+
+    private int fillRight(int pos, int overhang) {
+        int end = (getRight() - getLeft()) + overhang;
+
+        int nextLeft = getChildCount() == 0 ? 0 : getChildAt(getChildCount() - 1).getRight();
+        while (nextLeft < end && pos < mItemCount) {
+            pos += makeAndAddColumn(pos, nextLeft, true);
+            nextLeft = getChildAt(getChildCount() - 1).getRight();
+        }
+        final int gridRight = getWidth() - getPaddingRight();
+        return getChildAt(getChildCount() - 1).getRight() - gridRight;
+    }
+
+    private int fillLeft(int pos, int overhang) {
+        int end = getPaddingLeft() - overhang;
+
+        int nextRight = getChildAt(0).getLeft();
+        while (nextRight > end && pos >= 0) {
+            pos -= makeAndAddColumn(pos, nextRight, false);
+            nextRight = getChildAt(0).getLeft();
+        }
+
+        mFirstPosition = pos + 1;
+        return getPaddingLeft() - getChildAt(0).getLeft();
+    }
+
+    @Override
+    public void computeScroll() {
+        if (mScroller.computeScrollOffset()) {
+            final int x = mScroller.getCurrX();
+            final int dx = (int) (x - mLastTouchX);
+            mLastTouchX = x;
+            final boolean stopped = !trackMotionScroll(dx, false);
+
+            if (!stopped && !mScroller.isFinished()) {
+                ViewCompat.postInvalidateOnAnimation(this);
+            } else {
+                if (stopped) {
+                    final int overScrollMode = ViewCompat.getOverScrollMode(this);
+                    if (overScrollMode != ViewCompat.OVER_SCROLL_NEVER) {
+                        final EdgeEffectCompat edge;
+                        if (dx > 0) {
+                            edge = mLeftEdge;
+                        } else {
+                            edge = mRightEdge;
+                        }
+                        edge.onAbsorb(Math.abs((int) mScroller.getCurrVelocity()));
+                        ViewCompat.postInvalidateOnAnimation(this);
+                    }
+                    mScroller.abortAnimation();
+                }
+                mTouchMode = TOUCH_MODE_IDLE;
+            }
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+
+        if (!mLeftEdge.isFinished()) {
+            final int restoreCount = canvas.save();
+            final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+            canvas.rotate(270);
+            canvas.translate(-height + getPaddingTop(), 0);
+            mLeftEdge.setSize(height, getWidth());
+            if (mLeftEdge.draw(canvas)) {
+                postInvalidateOnAnimation();
+            }
+            canvas.restoreToCount(restoreCount);
+        }
+        if (!mRightEdge.isFinished()) {
+            final int restoreCount = canvas.save();
+            final int width = getWidth();
+            final int height = getHeight() - getPaddingTop() - getPaddingBottom();
+
+            canvas.rotate(90);
+            canvas.translate(-getPaddingTop(), width);
+            mRightEdge.setSize(height, width);
+            if (mRightEdge.draw(canvas)) {
+                postInvalidateOnAnimation();
+            }
+            canvas.restoreToCount(restoreCount);
+        }
+    }
+
+    /**
+     * Obtain a populated view from the adapter. If optScrap is non-null and is not
+     * reused it will be placed in the recycle bin.
+     *
+     * @param position position to get view for
+     * @param optScrap Optional scrap view; will be reused if possible
+     * @return A new view, a recycled view from mRecycler, or optScrap
+     */
+    private final View obtainView(int position, View optScrap) {
+        View view = mRecycler.getTransientStateView(position);
+        if (view != null) {
+            return view;
+        }
+
+        // Reuse optScrap if it's of the right type (and not null)
+        final int optType = optScrap != null ?
+                ((LayoutParams) optScrap.getLayoutParams()).viewType : -1;
+        final int positionViewType = mAdapter.getItemViewType(position);
+        final View scrap = optType == positionViewType ?
+                optScrap : mRecycler.getScrapView(positionViewType);
+
+        view = mAdapter.getView(position, scrap, this);
+
+        if (view != scrap && scrap != null) {
+            // The adapter didn't use it; put it back.
+            mRecycler.addScrap(scrap);
+        }
+
+        ViewGroup.LayoutParams lp = view.getLayoutParams();
+
+        if (view.getParent() != this) {
+            if (lp == null) {
+                lp = generateDefaultLayoutParams();
+            } else if (!checkLayoutParams(lp)) {
+                lp = generateLayoutParams(lp);
+            }
+            view.setLayoutParams(lp);
+        }
+
+        final LayoutParams sglp = (LayoutParams) lp;
+        sglp.position = position;
+        sglp.viewType = positionViewType;
+
+        return view;
+    }
+
+    public GalleryThumbnailAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    public void setAdapter(GalleryThumbnailAdapter adapter) {
+        if (mAdapter != null) {
+            mAdapter.unregisterDataSetObserver(mObserver);
+        }
+        // TODO: If the new adapter says that there are stable IDs, remove certain layout records
+        // and onscreen views if they have changed instead of removing all of the state here.
+        clearAllState();
+        mAdapter = adapter;
+        mDataChanged = true;
+        mOldItemCount = mItemCount = adapter != null ? adapter.getCount() : 0;
+        if (adapter != null) {
+            adapter.registerDataSetObserver(mObserver);
+            mRecycler.setViewTypeCount(adapter.getViewTypeCount());
+            mHasStableIds = adapter.hasStableIds();
+        } else {
+            mHasStableIds = false;
+        }
+        populate();
+    }
+
+    /**
+     * Clear all state because the grid will be used for a completely different set of data.
+     */
+    private void clearAllState() {
+        // Clear all layout records and views
+        removeAllViews();
+
+        // Reset to the top of the grid
+        mFirstPosition = 0;
+
+        // Clear recycler because there could be different view types now
+        mRecycler.clear();
+    }
+
+    @Override
+    protected LayoutParams generateDefaultLayoutParams() {
+        return new LayoutParams(LayoutParams.WRAP_CONTENT);
+    }
+
+    @Override
+    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+        return new LayoutParams(lp);
+    }
+
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams lp) {
+        return lp instanceof LayoutParams;
+    }
+
+    @Override
+    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
+        return new LayoutParams(getContext(), attrs);
+    }
+
+    public static class LayoutParams extends ViewGroup.LayoutParams {
+        private static final int[] LAYOUT_ATTRS = new int[] {
+                android.R.attr.layout_span
+        };
+
+        private static final int SPAN_INDEX = 0;
+
+        /**
+         * The number of columns this item should span
+         */
+        public int span = 1;
+
+        /**
+         * Item position this view represents
+         */
+        int position;
+
+        /**
+         * Type of this view as reported by the adapter
+         */
+        int viewType;
+
+        /**
+         * The column this view is occupying
+         */
+        int column;
+
+        /**
+         * The stable ID of the item this view displays
+         */
+        long id = -1;
+
+        public LayoutParams(int height) {
+            super(MATCH_PARENT, height);
+
+            if (this.height == MATCH_PARENT) {
+                Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
+                        "impossible! Falling back to WRAP_CONTENT");
+                this.height = WRAP_CONTENT;
+            }
+        }
+
+        public LayoutParams(Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            if (this.width != MATCH_PARENT) {
+                Log.w(TAG, "Inflation setting LayoutParams width to " + this.width +
+                        " - must be MATCH_PARENT");
+                this.width = MATCH_PARENT;
+            }
+            if (this.height == MATCH_PARENT) {
+                Log.w(TAG, "Inflation setting LayoutParams height to MATCH_PARENT - " +
+                        "impossible! Falling back to WRAP_CONTENT");
+                this.height = WRAP_CONTENT;
+            }
+
+            TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
+            span = a.getInteger(SPAN_INDEX, 1);
+            a.recycle();
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams other) {
+            super(other);
+
+            if (this.width != MATCH_PARENT) {
+                Log.w(TAG, "Constructing LayoutParams with width " + this.width +
+                        " - must be MATCH_PARENT");
+                this.width = MATCH_PARENT;
+            }
+            if (this.height == MATCH_PARENT) {
+                Log.w(TAG, "Constructing LayoutParams with height MATCH_PARENT - " +
+                        "impossible! Falling back to WRAP_CONTENT");
+                this.height = WRAP_CONTENT;
+            }
+        }
+    }
+
+    private class RecycleBin {
+        private ArrayList<View>[] mScrapViews;
+        private int mViewTypeCount;
+        private int mMaxScrap;
+
+        private SparseArray<View> mTransientStateViews;
+
+        public void setViewTypeCount(int viewTypeCount) {
+            if (viewTypeCount < 1) {
+                throw new IllegalArgumentException("Must have at least one view type (" +
+                        viewTypeCount + " types reported)");
+            }
+            if (viewTypeCount == mViewTypeCount) {
+                return;
+            }
+
+            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
+            for (int i = 0; i < viewTypeCount; i++) {
+                scrapViews[i] = new ArrayList<View>();
+            }
+            mViewTypeCount = viewTypeCount;
+            mScrapViews = scrapViews;
+        }
+
+        public void clear() {
+            final int typeCount = mViewTypeCount;
+            for (int i = 0; i < typeCount; i++) {
+                mScrapViews[i].clear();
+            }
+            if (mTransientStateViews != null) {
+                mTransientStateViews.clear();
+            }
+        }
+
+        public void clearTransientViews() {
+            if (mTransientStateViews != null) {
+                mTransientStateViews.clear();
+            }
+        }
+
+        public void addScrap(View v) {
+            final LayoutParams lp = (LayoutParams) v.getLayoutParams();
+            if (ViewCompat.hasTransientState(v)) {
+                if (mTransientStateViews == null) {
+                    mTransientStateViews = new SparseArray<View>();
+                }
+                mTransientStateViews.put(lp.position, v);
+                return;
+            }
+
+            final int childCount = getChildCount();
+            if (childCount > mMaxScrap) {
+                mMaxScrap = childCount;
+            }
+
+            ArrayList<View> scrap = mScrapViews[lp.viewType];
+            if (scrap.size() < mMaxScrap) {
+                scrap.add(v);
+            }
+        }
+
+        public View getTransientStateView(int position) {
+            if (mTransientStateViews == null) {
+                return null;
+            }
+
+            final View result = mTransientStateViews.get(position);
+            if (result != null) {
+                mTransientStateViews.remove(position);
+            }
+            return result;
+        }
+
+        public View getScrapView(int type) {
+            ArrayList<View> scrap = mScrapViews[type];
+            if (scrap.isEmpty()) {
+                return null;
+            }
+
+            final int index = scrap.size() - 1;
+            final View result = scrap.get(index);
+            scrap.remove(index);
+            return result;
+        }
+    }
+
+    private class AdapterDataSetObserver extends DataSetObserver {
+        @Override
+        public void onChanged() {
+            mDataChanged = true;
+            mOldItemCount = mItemCount;
+            mItemCount = mAdapter.getCount();
+
+            // TODO: Consider matching these back up if we have stable IDs.
+            mRecycler.clearTransientViews();
+
+            if (!mHasStableIds) {
+                recycleAllViews();
+            }
+
+            // TODO: consider repopulating in a deferred runnable instead
+            // (so that successive changes may still be batched)
+            requestLayout();
+        }
+
+        @Override
+        public void onInvalidated() {
+        }
+    }
+}
diff --git a/src/com/android/photos/views/HeaderGridView.java b/src/com/android/photos/views/HeaderGridView.java
new file mode 100644
index 0000000..45a5eaf
--- /dev/null
+++ b/src/com/android/photos/views/HeaderGridView.java
@@ -0,0 +1,466 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.database.DataSetObservable;
+import android.database.DataSetObserver;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.FrameLayout;
+import android.widget.GridView;
+import android.widget.ListAdapter;
+import android.widget.WrapperListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * A {@link GridView} that supports adding header rows in a
+ * very similar way to {@link ListView}.
+ * See {@link HeaderGridView#addHeaderView(View, Object, boolean)}
+ */
+public class HeaderGridView extends GridView {
+    private static final String TAG = "HeaderGridView";
+
+    /**
+     * A class that represents a fixed view in a list, for example a header at the top
+     * or a footer at the bottom.
+     */
+    private static class FixedViewInfo {
+        /** The view to add to the grid */
+        public View view;
+        public ViewGroup viewContainer;
+        /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
+        public Object data;
+        /** <code>true</code> if the fixed view should be selectable in the grid */
+        public boolean isSelectable;
+    }
+
+    private ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<FixedViewInfo>();
+
+    private void initHeaderGridView() {
+        super.setClipChildren(false);
+    }
+
+    public HeaderGridView(Context context) {
+        super(context);
+        initHeaderGridView();
+    }
+
+    public HeaderGridView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initHeaderGridView();
+    }
+
+    public HeaderGridView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        initHeaderGridView();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        ListAdapter adapter = getAdapter();
+        if (adapter != null && adapter instanceof HeaderViewGridAdapter) {
+            ((HeaderViewGridAdapter) adapter).setNumColumns(getNumColumns());
+        }
+    }
+
+    @Override
+    public void setClipChildren(boolean clipChildren) {
+       // Ignore, since the header rows depend on not being clipped
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the grid. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p>
+     * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
+     * the supplied cursor with one that will also account for header views.
+     *
+     * @param v The view to add.
+     * @param data Data to associate with this view
+     * @param isSelectable whether the item is selectable
+     */
+    public void addHeaderView(View v, Object data, boolean isSelectable) {
+        ListAdapter adapter = getAdapter();
+
+        if (adapter != null && ! (adapter instanceof HeaderViewGridAdapter)) {
+            throw new IllegalStateException(
+                    "Cannot add header view to grid -- setAdapter has already been called.");
+        }
+
+        FixedViewInfo info = new FixedViewInfo();
+        FrameLayout fl = new FullWidthFixedViewLayout(getContext());
+        fl.addView(v);
+        info.view = v;
+        info.viewContainer = fl;
+        info.data = data;
+        info.isSelectable = isSelectable;
+        mHeaderViewInfos.add(info);
+
+        // in the case of re-adding a header view, or adding one later on,
+        // we need to notify the observer
+        if (adapter != null) {
+            ((HeaderViewGridAdapter) adapter).notifyDataSetChanged();
+        }
+    }
+
+    /**
+     * Add a fixed view to appear at the top of the grid. If addHeaderView is
+     * called more than once, the views will appear in the order they were
+     * added. Views added using this call can take focus if they want.
+     * <p>
+     * NOTE: Call this before calling setAdapter. This is so HeaderGridView can wrap
+     * the supplied cursor with one that will also account for header views.
+     *
+     * @param v The view to add.
+     */
+    public void addHeaderView(View v) {
+        addHeaderView(v, null, true);
+    }
+
+    public int getHeaderViewCount() {
+        return mHeaderViewInfos.size();
+    }
+
+    /**
+     * Removes a previously-added header view.
+     *
+     * @param v The view to remove
+     * @return true if the view was removed, false if the view was not a header
+     *         view
+     */
+    public boolean removeHeaderView(View v) {
+        if (mHeaderViewInfos.size() > 0) {
+            boolean result = false;
+            ListAdapter adapter = getAdapter();
+            if (adapter != null && ((HeaderViewGridAdapter) adapter).removeHeader(v)) {
+                result = true;
+            }
+            removeFixedViewInfo(v, mHeaderViewInfos);
+            return result;
+        }
+        return false;
+    }
+
+    private void removeFixedViewInfo(View v, ArrayList<FixedViewInfo> where) {
+        int len = where.size();
+        for (int i = 0; i < len; ++i) {
+            FixedViewInfo info = where.get(i);
+            if (info.view == v) {
+                where.remove(i);
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void setAdapter(ListAdapter adapter) {
+        if (mHeaderViewInfos.size() > 0) {
+            HeaderViewGridAdapter hadapter = new HeaderViewGridAdapter(mHeaderViewInfos, adapter);
+            int numColumns = getNumColumns();
+            if (numColumns > 1) {
+                hadapter.setNumColumns(numColumns);
+            }
+            super.setAdapter(hadapter);
+        } else {
+            super.setAdapter(adapter);
+        }
+    }
+
+    private class FullWidthFixedViewLayout extends FrameLayout {
+        public FullWidthFixedViewLayout(Context context) {
+            super(context);
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            int targetWidth = HeaderGridView.this.getMeasuredWidth()
+                    - HeaderGridView.this.getPaddingLeft()
+                    - HeaderGridView.this.getPaddingRight();
+            widthMeasureSpec = MeasureSpec.makeMeasureSpec(targetWidth,
+                    MeasureSpec.getMode(widthMeasureSpec));
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    /**
+     * ListAdapter used when a HeaderGridView has header views. This ListAdapter
+     * wraps another one and also keeps track of the header views and their
+     * associated data objects.
+     *<p>This is intended as a base class; you will probably not need to
+     * use this class directly in your own code.
+     */
+    private static class HeaderViewGridAdapter implements WrapperListAdapter, Filterable {
+
+        // This is used to notify the container of updates relating to number of columns
+        // or headers changing, which changes the number of placeholders needed
+        private final DataSetObservable mDataSetObservable = new DataSetObservable();
+
+        private final ListAdapter mAdapter;
+        private int mNumColumns = 1;
+
+        // This ArrayList is assumed to NOT be null.
+        ArrayList<FixedViewInfo> mHeaderViewInfos;
+
+        boolean mAreAllFixedViewsSelectable;
+
+        private final boolean mIsFilterable;
+
+        public HeaderViewGridAdapter(ArrayList<FixedViewInfo> headerViewInfos, ListAdapter adapter) {
+            mAdapter = adapter;
+            mIsFilterable = adapter instanceof Filterable;
+
+            if (headerViewInfos == null) {
+                throw new IllegalArgumentException("headerViewInfos cannot be null");
+            }
+            mHeaderViewInfos = headerViewInfos;
+
+            mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);
+        }
+
+        public int getHeadersCount() {
+            return mHeaderViewInfos.size();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return (mAdapter == null || mAdapter.isEmpty()) && getHeadersCount() == 0;
+        }
+
+        public void setNumColumns(int numColumns) {
+            if (numColumns < 1) {
+                throw new IllegalArgumentException("Number of columns must be 1 or more");
+            }
+            if (mNumColumns != numColumns) {
+                mNumColumns = numColumns;
+                notifyDataSetChanged();
+            }
+        }
+
+        private boolean areAllListInfosSelectable(ArrayList<FixedViewInfo> infos) {
+            if (infos != null) {
+                for (FixedViewInfo info : infos) {
+                    if (!info.isSelectable) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        public boolean removeHeader(View v) {
+            for (int i = 0; i < mHeaderViewInfos.size(); i++) {
+                FixedViewInfo info = mHeaderViewInfos.get(i);
+                if (info.view == v) {
+                    mHeaderViewInfos.remove(i);
+
+                    mAreAllFixedViewsSelectable = areAllListInfosSelectable(mHeaderViewInfos);
+
+                    mDataSetObservable.notifyChanged();
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        @Override
+        public int getCount() {
+            if (mAdapter != null) {
+                return getHeadersCount() * mNumColumns + mAdapter.getCount();
+            } else {
+                return getHeadersCount() * mNumColumns;
+            }
+        }
+
+        @Override
+        public boolean areAllItemsEnabled() {
+            if (mAdapter != null) {
+                return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
+            } else {
+                return true;
+            }
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders) {
+                return (position % mNumColumns == 0)
+                        && mHeaderViewInfos.get(position / mNumColumns).isSelectable;
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.isEnabled(adjPosition);
+                }
+            }
+
+            throw new ArrayIndexOutOfBoundsException(position);
+        }
+
+        @Override
+        public Object getItem(int position) {
+            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders) {
+                if (position % mNumColumns == 0) {
+                    return mHeaderViewInfos.get(position / mNumColumns).data;
+                }
+                return null;
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.getItem(adjPosition);
+                }
+            }
+
+            throw new ArrayIndexOutOfBoundsException(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (mAdapter != null && position >= numHeadersAndPlaceholders) {
+                int adjPosition = position - numHeadersAndPlaceholders;
+                int adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.getItemId(adjPosition);
+                }
+            }
+            return -1;
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            if (mAdapter != null) {
+                return mAdapter.hasStableIds();
+            }
+            return false;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            // Header (negative positions will throw an ArrayIndexOutOfBoundsException)
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns ;
+            if (position < numHeadersAndPlaceholders) {
+                View headerViewContainer = mHeaderViewInfos
+                        .get(position / mNumColumns).viewContainer;
+                if (position % mNumColumns == 0) {
+                    return headerViewContainer;
+                } else {
+                    if (convertView == null) {
+                        convertView = new View(parent.getContext());
+                    }
+                    // We need to do this because GridView uses the height of the last item
+                    // in a row to determine the height for the entire row.
+                    convertView.setVisibility(View.INVISIBLE);
+                    convertView.setMinimumHeight(headerViewContainer.getHeight());
+                    return convertView;
+                }
+            }
+
+            // Adapter
+            final int adjPosition = position - numHeadersAndPlaceholders;
+            int adapterCount = 0;
+            if (mAdapter != null) {
+                adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.getView(adjPosition, convertView, parent);
+                }
+            }
+
+            throw new ArrayIndexOutOfBoundsException(position);
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            int numHeadersAndPlaceholders = getHeadersCount() * mNumColumns;
+            if (position < numHeadersAndPlaceholders && (position % mNumColumns != 0)) {
+                // Placeholders get the last view type number
+                return mAdapter != null ? mAdapter.getViewTypeCount() : 1;
+            }
+            if (mAdapter != null && position >= numHeadersAndPlaceholders) {
+                int adjPosition = position - numHeadersAndPlaceholders;
+                int adapterCount = mAdapter.getCount();
+                if (adjPosition < adapterCount) {
+                    return mAdapter.getItemViewType(adjPosition);
+                }
+            }
+
+            return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
+        }
+
+        @Override
+        public int getViewTypeCount() {
+            if (mAdapter != null) {
+                return mAdapter.getViewTypeCount() + 1;
+            }
+            return 2;
+        }
+
+        @Override
+        public void registerDataSetObserver(DataSetObserver observer) {
+            mDataSetObservable.registerObserver(observer);
+            if (mAdapter != null) {
+                mAdapter.registerDataSetObserver(observer);
+            }
+        }
+
+        @Override
+        public void unregisterDataSetObserver(DataSetObserver observer) {
+            mDataSetObservable.unregisterObserver(observer);
+            if (mAdapter != null) {
+                mAdapter.unregisterDataSetObserver(observer);
+            }
+        }
+
+        @Override
+        public Filter getFilter() {
+            if (mIsFilterable) {
+                return ((Filterable) mAdapter).getFilter();
+            }
+            return null;
+        }
+
+        @Override
+        public ListAdapter getWrappedAdapter() {
+            return mAdapter;
+        }
+
+        public void notifyDataSetChanged() {
+            mDataSetObservable.notifyChanged();
+        }
+    }
+}
diff --git a/src/com/android/photos/views/SquareImageView.java b/src/com/android/photos/views/SquareImageView.java
new file mode 100644
index 0000000..14eff10
--- /dev/null
+++ b/src/com/android/photos/views/SquareImageView.java
@@ -0,0 +1,53 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+
+public class SquareImageView extends ImageView {
+
+    public SquareImageView(Context context) {
+        super(context);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) {
+            int width = MeasureSpec.getSize(widthMeasureSpec);
+            int height = width;
+            if (heightMode == MeasureSpec.AT_MOST) {
+                height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
+            }
+            setMeasuredDimension(width, height);
+        } else {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
new file mode 100644
index 0000000..a1f7107
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageRenderer.java
@@ -0,0 +1,751 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.support.v4.util.LongSparseArray;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+import com.android.photos.data.GalleryBitmapPool;
+
+public class TiledImageRenderer {
+    public static final int SIZE_UNKNOWN = -1;
+
+    private static final String TAG = "TiledImageRenderer";
+    private static final int UPLOAD_LIMIT = 1;
+
+    /*
+     *  This is the tile state in the CPU side.
+     *  Life of a Tile:
+     *      ACTIVATED (initial state)
+     *              --> IN_QUEUE - by queueForDecode()
+     *              --> RECYCLED - by recycleTile()
+     *      IN_QUEUE --> DECODING - by decodeTile()
+     *               --> RECYCLED - by recycleTile)
+     *      DECODING --> RECYCLING - by recycleTile()
+     *               --> DECODED  - by decodeTile()
+     *               --> DECODE_FAIL - by decodeTile()
+     *      RECYCLING --> RECYCLED - by decodeTile()
+     *      DECODED --> ACTIVATED - (after the decoded bitmap is uploaded)
+     *      DECODED --> RECYCLED - by recycleTile()
+     *      DECODE_FAIL -> RECYCLED - by recycleTile()
+     *      RECYCLED --> ACTIVATED - by obtainTile()
+     */
+    private static final int STATE_ACTIVATED = 0x01;
+    private static final int STATE_IN_QUEUE = 0x02;
+    private static final int STATE_DECODING = 0x04;
+    private static final int STATE_DECODED = 0x08;
+    private static final int STATE_DECODE_FAIL = 0x10;
+    private static final int STATE_RECYCLING = 0x20;
+    private static final int STATE_RECYCLED = 0x40;
+
+    private static GalleryBitmapPool sTilePool = GalleryBitmapPool.getInstance();
+
+    // TILE_SIZE must be 2^N
+    private int mTileSize;
+
+    private TileSource mModel;
+    protected int mLevelCount;  // cache the value of mScaledBitmaps.length
+
+    // The mLevel variable indicates which level of bitmap we should use.
+    // Level 0 means the original full-sized bitmap, and a larger value means
+    // a smaller scaled bitmap (The width and height of each scaled bitmap is
+    // half size of the previous one). If the value is in [0, mLevelCount), we
+    // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value
+    // is mLevelCount
+    private int mLevel = 0;
+
+    private int mOffsetX;
+    private int mOffsetY;
+
+    private int mUploadQuota;
+    private boolean mRenderComplete;
+
+    private final RectF mSourceRect = new RectF();
+    private final RectF mTargetRect = new RectF();
+
+    private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>();
+
+    // The following three queue are guarded by mQueueLock
+    private final Object mQueueLock = new Object();
+    private final TileQueue mRecycledQueue = new TileQueue();
+    private final TileQueue mUploadQueue = new TileQueue();
+    private final TileQueue mDecodeQueue = new TileQueue();
+
+    // The width and height of the full-sized bitmap
+    protected int mImageWidth = SIZE_UNKNOWN;
+    protected int mImageHeight = SIZE_UNKNOWN;
+
+    protected int mCenterX;
+    protected int mCenterY;
+    protected float mScale;
+    protected int mRotation;
+
+    private boolean mLayoutTiles;
+
+    // Temp variables to avoid memory allocation
+    private final Rect mTileRange = new Rect();
+    private final Rect mActiveRange[] = {new Rect(), new Rect()};
+
+    private TileDecoder mTileDecoder;
+    private boolean mBackgroundTileUploaded;
+
+    private int mViewWidth, mViewHeight;
+    private View mParent;
+
+    public static interface TileSource {
+        public int getTileSize();
+        public int getImageWidth();
+        public int getImageHeight();
+
+        // The tile returned by this method can be specified this way: Assuming
+        // the image size is (width, height), first take the intersection of (0,
+        // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If
+        // in extending the region, we found some part of the region is outside
+        // the image, those pixels are filled with black.
+        //
+        // If level > 0, it does the same operation on a down-scaled version of
+        // the original image (down-scaled by a factor of 2^level), but (x, y)
+        // still refers to the coordinate on the original image.
+        //
+        // The method would be called by the decoder thread.
+        public Bitmap getTile(int level, int x, int y, Bitmap reuse);
+    }
+
+    public static int suggestedTileSize(Context context) {
+        return isHighResolution(context) ? 512 : 256;
+    }
+
+    private static boolean isHighResolution(Context context) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager wm = (WindowManager)
+                context.getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics.heightPixels > 2048 ||  metrics.widthPixels > 2048;
+    }
+
+    public TiledImageRenderer(View parent) {
+        mParent = parent;
+        mTileDecoder = new TileDecoder();
+        mTileDecoder.start();
+    }
+
+    public int getViewWidth() {
+        return mViewWidth;
+    }
+
+    public int getViewHeight() {
+        return mViewHeight;
+    }
+
+    private void invalidate() {
+        mParent.postInvalidate();
+    }
+
+    public void setModel(TileSource model, int rotation) {
+        if (mModel != model) {
+            mModel = model;
+            notifyModelInvalidated();
+        }
+        if (mRotation != rotation) {
+            mRotation = rotation;
+            mLayoutTiles = true;
+            invalidate();
+        }
+    }
+
+    private static int calulateLevelCount(TileSource source) {
+        int levels = 1;
+        int maxDim = Math.max(source.getImageWidth(), source.getImageHeight());
+        int t = source.getTileSize();
+        while (t < maxDim) {
+            t <<= 1;
+            levels++;
+        }
+        return levels;
+    }
+
+    public void notifyModelInvalidated() {
+        invalidateTiles();
+        if (mModel == null) {
+            mImageWidth = 0;
+            mImageHeight = 0;
+            mLevelCount = 0;
+        } else {
+            mImageWidth = mModel.getImageWidth();
+            mImageHeight = mModel.getImageHeight();
+            mLevelCount = calulateLevelCount(mModel);
+            mTileSize = mModel.getTileSize();
+        }
+        mLayoutTiles = true;
+        invalidate();
+    }
+
+    public void setViewSize(int width, int height) {
+        mViewWidth = width;
+        mViewHeight = height;
+    }
+
+    public void setPosition(int centerX, int centerY, float scale) {
+        if (mCenterX == centerX && mCenterY == centerY
+                && mScale == scale) return;
+        mCenterX = centerX;
+        mCenterY = centerY;
+        mScale = scale;
+        mLayoutTiles = true;
+        invalidate();
+    }
+
+    // Prepare the tiles we want to use for display.
+    //
+    // 1. Decide the tile level we want to use for display.
+    // 2. Decide the tile levels we want to keep as texture (in addition to
+    //    the one we use for display).
+    // 3. Recycle unused tiles.
+    // 4. Activate the tiles we want.
+    private void layoutTiles() {
+        if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) {
+            return;
+        }
+        mLayoutTiles = false;
+
+        // The tile levels we want to keep as texture is in the range
+        // [fromLevel, endLevel).
+        int fromLevel;
+        int endLevel;
+
+        // We want to use a texture larger than or equal to the display size.
+        mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount);
+
+        // We want to keep one more tile level as texture in addition to what
+        // we use for display. So it can be faster when the scale moves to the
+        // next level. We choose the level closest to the current scale.
+        if (mLevel != mLevelCount) {
+            Rect range = mTileRange;
+            getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation);
+            mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale);
+            mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale);
+            fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel;
+        } else {
+            // Activate the tiles of the smallest two levels.
+            fromLevel = mLevel - 2;
+            mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale);
+            mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale);
+        }
+
+        fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2));
+        endLevel = Math.min(fromLevel + 2, mLevelCount);
+
+        Rect range[] = mActiveRange;
+        for (int i = fromLevel; i < endLevel; ++i) {
+            getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation);
+        }
+
+        // If rotation is transient, don't update the tile.
+        if (mRotation % 90 != 0) return;
+
+        synchronized (mQueueLock) {
+            mDecodeQueue.clean();
+            mUploadQueue.clean();
+            mBackgroundTileUploaded = false;
+
+            // Recycle unused tiles: if the level of the active tile is outside the
+            // range [fromLevel, endLevel) or not in the visible range.
+            int n = mActiveTiles.size();
+            for (int i = 0; i < n; i++) {
+                Tile tile = mActiveTiles.valueAt(i);
+                int level = tile.mTileLevel;
+                if (level < fromLevel || level >= endLevel
+                        || !range[level - fromLevel].contains(tile.mX, tile.mY)) {
+                    mActiveTiles.removeAt(i);
+                    i--;
+                    n--;
+                    recycleTile(tile);
+                }
+            }
+        }
+
+        for (int i = fromLevel; i < endLevel; ++i) {
+            int size = mTileSize << i;
+            Rect r = range[i - fromLevel];
+            for (int y = r.top, bottom = r.bottom; y < bottom; y += size) {
+                for (int x = r.left, right = r.right; x < right; x += size) {
+                    activateTile(x, y, i);
+                }
+            }
+        }
+        invalidate();
+    }
+
+    private void invalidateTiles() {
+        synchronized (mQueueLock) {
+            mDecodeQueue.clean();
+            mUploadQueue.clean();
+
+            // TODO disable decoder
+            int n = mActiveTiles.size();
+            for (int i = 0; i < n; i++) {
+                Tile tile = mActiveTiles.valueAt(i);
+                recycleTile(tile);
+            }
+            mActiveTiles.clear();
+        }
+    }
+
+    private void getRange(Rect out, int cX, int cY, int level, int rotation) {
+        getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation);
+    }
+
+    // If the bitmap is scaled by the given factor "scale", return the
+    // rectangle containing visible range. The left-top coordinate returned is
+    // aligned to the tile boundary.
+    //
+    // (cX, cY) is the point on the original bitmap which will be put in the
+    // center of the ImageViewer.
+    private void getRange(Rect out,
+            int cX, int cY, int level, float scale, int rotation) {
+
+        double radians = Math.toRadians(-rotation);
+        double w = mViewWidth;
+        double h = mViewHeight;
+
+        double cos = Math.cos(radians);
+        double sin = Math.sin(radians);
+        int width = (int) Math.ceil(Math.max(
+                Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h)));
+        int height = (int) Math.ceil(Math.max(
+                Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h)));
+
+        int left = (int) Math.floor(cX - width / (2f * scale));
+        int top = (int) Math.floor(cY - height / (2f * scale));
+        int right = (int) Math.ceil(left + width / scale);
+        int bottom = (int) Math.ceil(top + height / scale);
+
+        // align the rectangle to tile boundary
+        int size = mTileSize << level;
+        left = Math.max(0, size * (left / size));
+        top = Math.max(0, size * (top / size));
+        right = Math.min(mImageWidth, right);
+        bottom = Math.min(mImageHeight, bottom);
+
+        out.set(left, top, right, bottom);
+    }
+
+    public void freeTextures() {
+        mLayoutTiles = true;
+
+        synchronized (mQueueLock) {
+            mUploadQueue.clean();
+            mDecodeQueue.clean();
+            Tile tile = mRecycledQueue.pop();
+            while (tile != null) {
+                tile.recycle();
+                tile = mRecycledQueue.pop();
+            }
+        }
+
+        int n = mActiveTiles.size();
+        for (int i = 0; i < n; i++) {
+            Tile texture = mActiveTiles.valueAt(i);
+            texture.recycle();
+        }
+        mActiveTiles.clear();
+        mTileRange.set(0, 0, 0, 0);
+
+        if (sTilePool != null) sTilePool.clear();
+    }
+
+    public void draw(GLCanvas canvas) {
+        layoutTiles();
+        uploadTiles(canvas);
+
+        mUploadQuota = UPLOAD_LIMIT;
+        mRenderComplete = true;
+
+        int level = mLevel;
+        int rotation = mRotation;
+        int flags = 0;
+        if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX;
+
+        if (flags != 0) {
+            canvas.save(flags);
+            if (rotation != 0) {
+                int centerX = mViewWidth / 2, centerY = mViewHeight / 2;
+                canvas.translate(centerX, centerY);
+                canvas.rotate(rotation, 0, 0, 1);
+                canvas.translate(-centerX, -centerY);
+            }
+        }
+        try {
+            if (level != mLevelCount) {
+                int size = (mTileSize << level);
+                float length = size * mScale;
+                Rect r = mTileRange;
+
+                for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) {
+                    float y = mOffsetY + i * length;
+                    for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) {
+                        float x = mOffsetX + j * length;
+                        drawTile(canvas, tx, ty, level, x, y, length);
+                    }
+                }
+            }
+        } finally {
+            if (flags != 0) canvas.restore();
+        }
+
+        if (mRenderComplete) {
+            if (!mBackgroundTileUploaded) {
+                uploadBackgroundTiles(canvas);
+            }
+        } else {
+            invalidate();
+        }
+    }
+
+    private void uploadBackgroundTiles(GLCanvas canvas) {
+        mBackgroundTileUploaded = true;
+        int n = mActiveTiles.size();
+        for (int i = 0; i < n; i++) {
+            Tile tile = mActiveTiles.valueAt(i);
+            if (!tile.isContentValid()) {
+                queueForDecode(tile);
+            }
+        }
+    }
+
+    private void queueForUpload(Tile tile) {
+        synchronized (mQueueLock) {
+            mUploadQueue.push(tile);
+        }
+        invalidate();
+        // TODO
+//        if (mTileUploader.mActive.compareAndSet(false, true)) {
+//            getGLRoot().addOnGLIdleListener(mTileUploader);
+//        }
+    }
+
+   private void queueForDecode(Tile tile) {
+       synchronized (mQueueLock) {
+           if (tile.mTileState == STATE_ACTIVATED) {
+               tile.mTileState = STATE_IN_QUEUE;
+               if (mDecodeQueue.push(tile)) {
+                   mQueueLock.notifyAll();
+               }
+           }
+       }
+    }
+
+    private boolean decodeTile(Tile tile) {
+        synchronized (mQueueLock) {
+            if (tile.mTileState != STATE_IN_QUEUE) return false;
+            tile.mTileState = STATE_DECODING;
+        }
+        boolean decodeComplete = tile.decode();
+        synchronized (mQueueLock) {
+            if (tile.mTileState == STATE_RECYCLING) {
+                tile.mTileState = STATE_RECYCLED;
+                if (tile.mDecodedTile != null) {
+                    if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
+                    tile.mDecodedTile = null;
+                }
+                mRecycledQueue.push(tile);
+                return false;
+            }
+            tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL;
+            return decodeComplete;
+        }
+    }
+
+    private Tile obtainTile(int x, int y, int level) {
+        synchronized (mQueueLock) {
+            Tile tile = mRecycledQueue.pop();
+            if (tile != null) {
+                tile.mTileState = STATE_ACTIVATED;
+                tile.update(x, y, level);
+                return tile;
+            }
+            return new Tile(x, y, level);
+        }
+    }
+
+    private void recycleTile(Tile tile) {
+        synchronized (mQueueLock) {
+            if (tile.mTileState == STATE_DECODING) {
+                tile.mTileState = STATE_RECYCLING;
+                return;
+            }
+            tile.mTileState = STATE_RECYCLED;
+            if (tile.mDecodedTile != null) {
+                if (sTilePool != null) sTilePool.put(tile.mDecodedTile);
+                tile.mDecodedTile = null;
+            }
+            mRecycledQueue.push(tile);
+        }
+    }
+
+    private void activateTile(int x, int y, int level) {
+        long key = makeTileKey(x, y, level);
+        Tile tile = mActiveTiles.get(key);
+        if (tile != null) {
+            if (tile.mTileState == STATE_IN_QUEUE) {
+                tile.mTileState = STATE_ACTIVATED;
+            }
+            return;
+        }
+        tile = obtainTile(x, y, level);
+        mActiveTiles.put(key, tile);
+    }
+
+    private Tile getTile(int x, int y, int level) {
+        return mActiveTiles.get(makeTileKey(x, y, level));
+    }
+
+    private static long makeTileKey(int x, int y, int level) {
+        long result = x;
+        result = (result << 16) | y;
+        result = (result << 16) | level;
+        return result;
+    }
+
+    private void uploadTiles(GLCanvas canvas) {
+        int quota = UPLOAD_LIMIT;
+        Tile tile = null;
+        while (quota > 0) {
+            synchronized (mQueueLock) {
+                tile = mUploadQueue.pop();
+            }
+            if (tile == null) break;
+            if (!tile.isContentValid()) {
+                Utils.assertTrue(tile.mTileState == STATE_DECODED);
+                tile.updateContent(canvas);
+                --quota;
+            }
+        }
+        if (tile != null) {
+            invalidate();
+        }
+    }
+
+    // Draw the tile to a square at canvas that locates at (x, y) and
+    // has a side length of length.
+    private void drawTile(GLCanvas canvas,
+            int tx, int ty, int level, float x, float y, float length) {
+        RectF source = mSourceRect;
+        RectF target = mTargetRect;
+        target.set(x, y, x + length, y + length);
+        source.set(0, 0, mTileSize, mTileSize);
+
+        Tile tile = getTile(tx, ty, level);
+        if (tile != null) {
+            if (!tile.isContentValid()) {
+                if (tile.mTileState == STATE_DECODED) {
+                    if (mUploadQuota > 0) {
+                        --mUploadQuota;
+                        tile.updateContent(canvas);
+                    } else {
+                        mRenderComplete = false;
+                    }
+                } else if (tile.mTileState != STATE_DECODE_FAIL){
+                    mRenderComplete = false;
+                    queueForDecode(tile);
+                }
+            }
+            drawTile(tile, canvas, source, target);
+        }
+    }
+
+    private boolean drawTile(
+            Tile tile, GLCanvas canvas, RectF source, RectF target) {
+        while (true) {
+            if (tile.isContentValid()) {
+                canvas.drawTexture(tile, source, target);
+                return true;
+            }
+
+            // Parent can be divided to four quads and tile is one of the four.
+            Tile parent = tile.getParentTile();
+            if (parent == null) return false;
+            if (tile.mX == parent.mX) {
+                source.left /= 2f;
+                source.right /= 2f;
+            } else {
+                source.left = (mTileSize + source.left) / 2f;
+                source.right = (mTileSize + source.right) / 2f;
+            }
+            if (tile.mY == parent.mY) {
+                source.top /= 2f;
+                source.bottom /= 2f;
+            } else {
+                source.top = (mTileSize + source.top) / 2f;
+                source.bottom = (mTileSize + source.bottom) / 2f;
+            }
+            tile = parent;
+        }
+    }
+
+    private class Tile extends UploadedTexture {
+        public int mX;
+        public int mY;
+        public int mTileLevel;
+        public Tile mNext;
+        public Bitmap mDecodedTile;
+        public volatile int mTileState = STATE_ACTIVATED;
+
+        public Tile(int x, int y, int level) {
+            mX = x;
+            mY = y;
+            mTileLevel = level;
+        }
+
+        @Override
+        protected void onFreeBitmap(Bitmap bitmap) {
+            if (sTilePool != null) sTilePool.put(bitmap);
+        }
+
+        boolean decode() {
+            // Get a tile from the original image. The tile is down-scaled
+            // by (1 << mTilelevel) from a region in the original image.
+            try {
+                Bitmap reuse = sTilePool.get(mTileSize, mTileSize);
+                mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse);
+            } catch (Throwable t) {
+                Log.w(TAG, "fail to decode tile", t);
+            }
+            return mDecodedTile != null;
+        }
+
+        @Override
+        protected Bitmap onGetBitmap() {
+            Utils.assertTrue(mTileState == STATE_DECODED);
+
+            // We need to override the width and height, so that we won't
+            // draw beyond the boundaries.
+            int rightEdge = ((mImageWidth - mX) >> mTileLevel);
+            int bottomEdge = ((mImageHeight - mY) >> mTileLevel);
+            setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge));
+
+            Bitmap bitmap = mDecodedTile;
+            mDecodedTile = null;
+            mTileState = STATE_ACTIVATED;
+            return bitmap;
+        }
+
+        // We override getTextureWidth() and getTextureHeight() here, so the
+        // texture can be re-used for different tiles regardless of the actual
+        // size of the tile (which may be small because it is a tile at the
+        // boundary).
+        @Override
+        public int getTextureWidth() {
+            return mTileSize;
+        }
+
+        @Override
+        public int getTextureHeight() {
+            return mTileSize;
+        }
+
+        public void update(int x, int y, int level) {
+            mX = x;
+            mY = y;
+            mTileLevel = level;
+            invalidateContent();
+        }
+
+        public Tile getParentTile() {
+            if (mTileLevel + 1 == mLevelCount) return null;
+            int size = mTileSize << (mTileLevel + 1);
+            int x = size * (mX / size);
+            int y = size * (mY / size);
+            return getTile(x, y, mTileLevel + 1);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("tile(%s, %s, %s / %s)",
+                    mX / mTileSize, mY / mTileSize, mLevel, mLevelCount);
+        }
+    }
+
+    private static class TileQueue {
+        private Tile mHead;
+
+        public Tile pop() {
+            Tile tile = mHead;
+            if (tile != null) mHead = tile.mNext;
+            return tile;
+        }
+
+        public boolean push(Tile tile) {
+            boolean wasEmpty = mHead == null;
+            tile.mNext = mHead;
+            mHead = tile;
+            return wasEmpty;
+        }
+
+        public void clean() {
+            mHead = null;
+        }
+    }
+
+    private class TileDecoder extends Thread {
+
+        public void finishAndWait() {
+            interrupt();
+            try {
+                join();
+            } catch (InterruptedException e) {
+                Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!");
+            }
+        }
+
+        private Tile waitForTile() throws InterruptedException {
+            synchronized(mQueueLock) {
+                while (true) {
+                    Tile tile = mDecodeQueue.pop();
+                    if (tile != null) {
+                        return tile;
+                    }
+                    mQueueLock.wait();
+                }
+            }
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!isInterrupted()) {
+                    Tile tile = waitForTile();
+                    if (decodeTile(tile)) {
+                        queueForUpload(tile);
+                    }
+                }
+            } catch (InterruptedException ex) {
+            }
+        }
+
+    }
+}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
new file mode 100644
index 0000000..6fe030d
--- /dev/null
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -0,0 +1,269 @@
+/*
+ * 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.photos.views;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.opengl.GLSurfaceView.Renderer;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.widget.FrameLayout;
+import com.android.gallery3d.glrenderer.GLES20Canvas;
+import com.android.photos.views.TiledImageRenderer.TileSource;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+
+public class TiledImageView extends FrameLayout implements OnScaleGestureListener {
+
+    private BlockingGLTextureView mTextureView;
+    private float mLastX, mLastY;
+
+    private static class ImageRendererWrapper {
+        // Guarded by locks
+        float scale;
+        int centerX, centerY;
+        int rotation;
+        TileSource source;
+
+        // GL thread only
+        TiledImageRenderer image;
+    }
+
+    // TODO: left/right paging
+    private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1];
+    private ImageRendererWrapper mFocusedRenderer;
+
+    // -------------------------
+    // Guarded by mLock
+    // -------------------------
+    private Object mLock = new Object();
+    private ScaleGestureDetector mScaleGestureDetector;
+
+    public TiledImageView(Context context) {
+        this(context, null);
+    }
+
+    public TiledImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mTextureView = new BlockingGLTextureView(context);
+        addView(mTextureView, new LayoutParams(
+                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+        mTextureView.setRenderer(new TileRenderer());
+        setTileSource(new ColoredTiles());
+        mScaleGestureDetector = new ScaleGestureDetector(context, this);
+    }
+
+    public void destroy() {
+        mTextureView.destroy();
+    }
+
+    public void setTileSource(TileSource source) {
+        synchronized (mLock) {
+            for (int i = 0; i < mRenderers.length; i++) {
+                ImageRendererWrapper renderer = mRenderers[i];
+                if (renderer == null) {
+                    renderer = mRenderers[i] = new ImageRendererWrapper();
+                }
+                renderer.source = source;
+                renderer.centerX = renderer.source.getImageWidth() / 2;
+                renderer.centerY = renderer.source.getImageHeight() / 2;
+                renderer.rotation = 0;
+                renderer.scale = 0;
+                renderer.image = new TiledImageRenderer(this);
+                updateScaleIfNecessaryLocked(renderer);
+            }
+        }
+        mFocusedRenderer = mRenderers[0];
+        invalidate();
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        return true;
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        // Don't need the lock because this will only fire inside of onTouchEvent
+        mFocusedRenderer.scale *= detector.getScaleFactor();
+        invalidate();
+        return true;
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        int action = event.getActionMasked();
+        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
+        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
+
+        // Determine focal point
+        float sumX = 0, sumY = 0;
+        final int count = event.getPointerCount();
+        for (int i = 0; i < count; i++) {
+            if (skipIndex == i) continue;
+            sumX += event.getX(i);
+            sumY += event.getY(i);
+        }
+        final int div = pointerUp ? count - 1 : count;
+        float x = sumX / div;
+        float y = sumY / div;
+
+        synchronized (mLock) {
+            mScaleGestureDetector.onTouchEvent(event);
+            switch (action) {
+            case MotionEvent.ACTION_MOVE:
+                mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale;
+                mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale;
+                invalidate();
+                break;
+            }
+        }
+
+        mLastX = x;
+        mLastY = y;
+        return true;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right,
+            int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        synchronized (mLock) {
+            for (ImageRendererWrapper renderer : mRenderers) {
+                updateScaleIfNecessaryLocked(renderer);
+            }
+        }
+    }
+
+    private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
+        if (renderer.scale > 0 || getWidth() == 0) return;
+        renderer.scale = Math.min(
+                (float) getWidth() / (float) renderer.source.getImageWidth(),
+                (float) getHeight() / (float) renderer.source.getImageHeight());
+    }
+
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        mTextureView.render();
+        super.dispatchDraw(canvas);
+    }
+
+    @Override
+    public void invalidate() {
+        super.invalidate();
+        mTextureView.invalidate();
+    }
+
+    private class TileRenderer implements Renderer {
+
+        private GLES20Canvas mCanvas;
+
+        @Override
+        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+            mCanvas = new GLES20Canvas();
+            for (ImageRendererWrapper renderer : mRenderers) {
+                renderer.image.setModel(renderer.source, renderer.rotation);
+            }
+        }
+
+        @Override
+        public void onSurfaceChanged(GL10 gl, int width, int height) {
+            mCanvas.setSize(width, height);
+            for (ImageRendererWrapper renderer : mRenderers) {
+                renderer.image.setViewSize(width, height);
+            }
+        }
+
+        @Override
+        public void onDrawFrame(GL10 gl) {
+            mCanvas.clearBuffer();
+            synchronized (mLock) {
+                for (ImageRendererWrapper renderer : mRenderers) {
+                    renderer.image.setModel(renderer.source, renderer.rotation);
+                    renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale);
+                }
+            }
+            for (ImageRendererWrapper renderer : mRenderers) {
+                renderer.image.draw(mCanvas);
+            }
+        }
+
+    }
+
+    private static class ColoredTiles implements TileSource {
+        private static int[] COLORS = new int[] {
+            Color.RED,
+            Color.BLUE,
+            Color.YELLOW,
+            Color.GREEN,
+            Color.CYAN,
+            Color.MAGENTA,
+            Color.WHITE,
+        };
+
+        private Paint mPaint = new Paint();
+        private Canvas mCanvas = new Canvas();
+
+        @Override
+        public int getTileSize() {
+            return 256;
+        }
+
+        @Override
+        public int getImageWidth() {
+            return 16384;
+        }
+
+        @Override
+        public int getImageHeight() {
+            return 8192;
+        }
+
+        @Override
+        public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+            int tileSize = getTileSize();
+            if (bitmap == null) {
+                bitmap = Bitmap.createBitmap(tileSize, tileSize,
+                        Bitmap.Config.ARGB_8888);
+            }
+            mCanvas.setBitmap(bitmap);
+            mCanvas.drawColor(COLORS[level]);
+            mPaint.setColor(Color.BLACK);
+            mPaint.setTextSize(20);
+            mPaint.setTextAlign(Align.CENTER);
+            mCanvas.drawText(x + "x" + y, 128, 128, mPaint);
+            tileSize <<= level;
+            x /= tileSize;
+            y /= tileSize;
+            mCanvas.drawText(x + "x" + y + " @ " + level, 128, 30, mPaint);
+            mCanvas.setBitmap(null);
+            return bitmap;
+        }
+    }
+}
diff --git a/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java b/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java
new file mode 100644
index 0000000..2a39b68
--- /dev/null
+++ b/src_pd/com/android/gallery3d/filtershow/editors/EditorManager.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.filtershow.editors;
+
+import com.android.gallery3d.filtershow.EditorPlaceHolder;
+import com.android.gallery3d.filtershow.editors.BasicEditor;
+import com.android.gallery3d.filtershow.editors.EditorCurves;
+import com.android.gallery3d.filtershow.editors.EditorZoom;
+
+public class EditorManager {
+
+    public static void addEditors(EditorPlaceHolder editorPlaceHolder) {
+        editorPlaceHolder.addEditor(new EditorZoom());
+        editorPlaceHolder.addEditor(new EditorCurves());
+        editorPlaceHolder.addEditor(new EditorTinyPlanet());
+        editorPlaceHolder.addEditor(new EditorDraw());
+        editorPlaceHolder.addEditor(new EditorVignette());
+        editorPlaceHolder.addEditor(new EditorFlip());
+        editorPlaceHolder.addEditor(new EditorRotate());
+        editorPlaceHolder.addEditor(new EditorStraighten());
+        editorPlaceHolder.addEditor(new EditorCrop());
+    }
+
+}
diff --git a/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java
new file mode 100644
index 0000000..97588db
--- /dev/null
+++ b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java
@@ -0,0 +1,92 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
+
+import com.android.gallery3d.R;
+
+import java.util.HashMap;
+import java.util.Vector;
+
+public class FiltersManager extends BaseFiltersManager {
+    private static FiltersManager sInstance = null;
+    private static FiltersManager sPreviewInstance = null;
+    private static FiltersManager sHighresInstance = null;
+    private static int mImageBorderSize = 4; // in percent
+    public FiltersManager() {
+        init();
+    }
+
+    public static FiltersManager getPreviewManager() {
+        if (sPreviewInstance == null) {
+            sPreviewInstance = new FiltersManager();
+        }
+        return sPreviewInstance;
+    }
+
+    public static FiltersManager getManager() {
+        if (sInstance == null) {
+            sInstance = new FiltersManager();
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void addBorders(Context context, Vector<FilterRepresentation> representations) {
+        // Regular borders
+        representations.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5));
+        representations.add(
+                new FilterImageBorderRepresentation(R.drawable.filtershow_border_brush));
+        representations.add(
+                new FilterImageBorderRepresentation(R.drawable.filtershow_border_grunge));
+        representations.add(
+                new FilterImageBorderRepresentation(R.drawable.filtershow_border_sumi_e));
+        representations.add(new FilterImageBorderRepresentation(R.drawable.filtershow_border_tape));
+        representations.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, 0));
+        representations.add(new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize,
+                mImageBorderSize));
+        representations.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, 0));
+        representations.add(new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize,
+                mImageBorderSize));
+        int creamColor = Color.argb(255, 237, 237, 227);
+        representations.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize, 0));
+        representations.add(new FilterColorBorderRepresentation(creamColor, mImageBorderSize,
+                mImageBorderSize));
+    }
+
+    public static FiltersManager getHighresManager() {
+        if (sHighresInstance == null) {
+            sHighresInstance = new FiltersManager();
+        }
+        return sHighresInstance;
+    }
+
+    public static void reset() {
+        sInstance = null;
+        sPreviewInstance = null;
+        sHighresInstance = null;
+    }
+
+    public static void setResources(Resources resources) {
+        FiltersManager.getManager().setFilterResources(resources);
+        FiltersManager.getPreviewManager().setFilterResources(resources);
+        FiltersManager.getHighresManager().setFilterResources(resources);
+    }
+}
diff --git a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
index 0418291..5e800e2 100644
--- a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
+++ b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
@@ -28,7 +28,6 @@
 import com.android.gallery3d.data.MediaSource;
 import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.PathMatcher;
-import com.android.gallery3d.exif.ExifData;
 
 import java.io.FileNotFoundException;
 
@@ -143,8 +142,6 @@
 
     public static void onPackageChanged(Context context, String packageName) {/*do nothing*/}
 
-    public static void extractExifValues(MediaObject item, ExifData exif) {/*do nothing*/}
-
     public static Dialog getVersionCheckDialog(Activity activity){
         return null;
     }
diff --git a/src_pd/com/android/gallery3d/util/UsageStatistics.java b/src_pd/com/android/gallery3d/util/UsageStatistics.java
new file mode 100644
index 0000000..868bbb6
--- /dev/null
+++ b/src_pd/com/android/gallery3d/util/UsageStatistics.java
@@ -0,0 +1,48 @@
+/*
+ * 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.gallery3d.util;
+
+import android.content.Context;
+
+public class UsageStatistics {
+
+    public static final String COMPONENT_GALLERY = "Gallery";
+    public static final String COMPONENT_CAMERA = "Camera";
+    public static final String COMPONENT_EDITOR = "Editor";
+    public static final String COMPONENT_IMPORTER = "Importer";
+
+    public static final String TRANSITION_BACK_BUTTON = "BackButton";
+    public static final String TRANSITION_UP_BUTTON = "UpButton";
+    public static final String TRANSITION_PINCH_IN = "PinchIn";
+    public static final String TRANSITION_PINCH_OUT = "PinchOut";
+    public static final String TRANSITION_INTENT = "Intent";
+    public static final String TRANSITION_ITEM_TAP = "ItemTap";
+    public static final String TRANSITION_MENU_TAP = "MenuTap";
+    public static final String TRANSITION_BUTTON_TAP = "ButtonTap";
+    public static final String TRANSITION_SWIPE = "Swipe";
+
+    public static final String ACTION_CAPTURE_START = "CaptureStart";
+    public static final String ACTION_CAPTURE_FAIL = "CaptureFail";
+    public static final String ACTION_CAPTURE_DONE = "CaptureDone";
+    public static final String ACTION_SHARE = "Share";
+
+    public static void initialize(Context context) {}
+    public static void setPendingTransitionCause(String cause) {}
+    public static void onContentViewChanged(String screenComponent, String screenName) {}
+    public static void onEvent(String category, String action, String label) {};
+    public static void onEvent(String category, String action, String label, long optional_value) {};
+}
diff --git a/src_pd/com/android/photos/data/PhotoProviderAuthority.java b/src_pd/com/android/photos/data/PhotoProviderAuthority.java
new file mode 100644
index 0000000..0ac76cb
--- /dev/null
+++ b/src_pd/com/android/photos/data/PhotoProviderAuthority.java
@@ -0,0 +1,21 @@
+/*
+ * 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.photos.data;
+
+interface PhotoProviderAuthority {
+    public static final String AUTHORITY = "com.android.gallery3d.photoprovider";
+}
diff --git a/tests/Android.mk b/tests/Android.mk
index 22bb7ee..0cc5f87 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -6,6 +6,8 @@
 
 LOCAL_SDK_VERSION := 16
 
+LOCAL_STATIC_JAVA_LIBRARIES := littlemock dexmaker
+
 # Include all test java files.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 1d53d4d..b98b5e0 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -25,7 +25,19 @@
              android:targetPackage="com.android.gallery3d"
              android:label="Tests for GalleryNew3D application."/>
 
+    <instrumentation android:name="com.android.gallery3d.CameraTestRunner"
+            android:targetPackage="com.android.gallery3d"
+            android:label="Camera continuous test runner"/>
+
     <instrumentation android:name="com.android.gallery3d.exif.ExifTestRunner"
             android:targetPackage="com.android.gallery3d"
             android:label="Tests for ExifParser."/>
+
+    <instrumentation android:name="com.android.gallery3d.stress.CameraStressTestRunner"
+            android:targetPackage="com.android.gallery3d"
+            android:label="Camera stress test runner"/>
+
+    <instrumentation android:name="com.android.photos.data.DataTestRunner"
+            android:targetPackage="com.android.gallery3d"
+            android:label="Tests for android photo DataProviders."/>
 </manifest>
diff --git a/tests/exiftool_parser/parser.py b/tests/exiftool_parser/parser.py
new file mode 100755
index 0000000..7df23f1
--- /dev/null
+++ b/tests/exiftool_parser/parser.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# This parser parses the output from Phil Harvey's exiftool (version 9.02)
+# and convert it to xml format. It reads exiftool's output from stdin and
+# write the xml format to stdout.
+#
+# In order to get the raw infomation from exiftool, we need to enable the verbose
+# flag (-v2) of exiftool.
+#
+# Usage:
+#      exiftool -v2 img.jpg | ./parser.py >> output.xml
+#
+#
+
+import os
+import sys
+import re
+
+text = sys.stdin.read()
+
+print """<?xml version="1.0" encoding="utf-8"?>"""
+print "<exif>"
+
+# find the following two groups of string:
+#
+# 1. tag:
+#
+# | | | x) name = value
+# | | |     - Tag 0x1234
+#
+# 2. IFD indicator:
+#
+# | | | + [xxx directory with xx entries]
+#
+p = re.compile(
+        "(((?:\| )+)[0-9]*\)(?:(?:.*? = .*?)|(?:.*? \(SubDirectory\) -->))\n.*?- Tag 0x[0-9a-f]{4})" + "|"
+        + "(((?:\| )*)\+ \[.*? directory with [0-9]+ entries]$)"
+        , re.M)
+tags = p.findall(text)
+
+layer = 0
+ifds = []
+
+for s in tags:
+    # IFD indicator
+    if s[2]:
+        l = len(s[3])
+        ifd = s[2][l + 3:].split()[0]
+        new_layer = l / 2 + 1
+        if new_layer > layer:
+            ifds.append(ifd)
+        else:
+            for i in range(layer - new_layer):
+                ifds.pop()
+            ifds[-1] = ifd
+        layer = new_layer
+    else:
+        l = len(s[1])
+        s = s[0]
+        new_layer = l / 2
+        if new_layer < layer:
+            for i in range(layer - new_layer):
+                ifds.pop()
+        layer = new_layer
+
+        # find the ID
+        _id = re.search("0x[0-9a-f]{4}", s)
+        _id = _id.group(0)
+
+        # find the name
+        name = re.search("[0-9]*?\).*?(?:(?: = )|(?: \(SubDirectory\) -->))", s)
+        name = name.group(0).split()[1]
+
+        # find the raw value in the parenthesis
+        value = re.search("\(SubDirectory\) -->", s)
+        if value:
+            value = "NO_VALUE"
+        else:
+            value = re.search("\(.*\)\n", s)
+            if (name != 'Model' and value):
+                value = value.group(0)[1:-2]
+            else:
+                value = re.search("=.*\n", s)
+                value = value.group(0)[2:-1]
+                if "[snip]" in value:
+                    value = "NO_VALUE"
+
+        print ('    <tag ifd="' + ifds[-1] + '" id="'
+            + _id + '" name="' + name +'">' + value + "</tag>")
+print "</exif>"
diff --git a/tests/res/raw/android_lawn.mp4 b/tests/res/raw/android_lawn.mp4
new file mode 100644
index 0000000..bdeffbe
--- /dev/null
+++ b/tests/res/raw/android_lawn.mp4
Binary files differ
diff --git a/tests/res/xml/galaxy_nexus.xml b/tests/res/xml/galaxy_nexus.xml
index cefd078..55dd524 100644
--- a/tests/res/xml/galaxy_nexus.xml
+++ b/tests/res/xml/galaxy_nexus.xml
@@ -1,48 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
 <exif>
-    <ifd name="ifd0">
-        <tag id="0x100" name="ImageWidth">2560</tag>
-        <tag id="0x101" name="ImageHeight">1920</tag>
-        <tag id="0x10f" name="Make">google</tag>
-        <tag id="0x110" name="Model">Nexus S</tag>
-        <tag id="0x112" name="Orientation">1</tag>
-        <tag id="0x131" name="Software">MASTER</tag>
-        <tag id="0x132" name="DateTime">2012:07:30 16:28:42</tag>
-        <tag id="0x213" name="YCbCrPositioning">1</tag>
-        <tag id="0x8769" name="ExifOffset">164</tag>
-    </ifd>
-    <ifd name="exif-ifd">
-        <tag id="0x829A" name="ExposureTime">1/40</tag>
-        <tag id="0x829D" name="FNumber">26/10</tag>
-        <tag id="0x8822" name="ExposureProgram">3</tag>
-        <tag id="0x8827" name="IsoSpeedRatings">100</tag>
-        <tag id="0x9000" name="ExifVersion">30 32 32 30</tag>
-        <tag id="0x9003" name="DateTimeTimeOriginal">2012:07:30 16:28:42</tag>
-        <tag id="0x9004" name="DateTimeTimeDigitized">2012:07:30 16:28:42</tag>
-        <tag id="0x9201" name="ShutterSpeed">50/10</tag>
-        <tag id="0x9202" name="ApertureValue">30/10</tag>
-        <tag id="0x9203" name="BrightnessValue">30/10</tag>
-        <tag id="0x9204" name="ExposureBiasValue">0/0</tag>
-        <tag id="0x9205" name="MaxApertureValue">30/10</tag>
-        <tag id="0x9207" name="MeteringMode">2</tag>
-        <tag id="0x9209" name="Flash">0</tag>
-        <tag id="0x920A" name="FocalLength">343/100</tag>
-        <tag id="0x9286" name="UserComment">00 00 00 49 49 43 53 41 55 73 65 72 20 63 6f 6d 6d 65 6e 74 73 00</tag>
-        <tag id="0xA001" name="ColoSpace">1</tag>
-        <tag id="0xA002" name="PixelXDimension">2560</tag>
-        <tag id="0xA003" name="PixelYDimension">1920</tag>
-        <tag id="0xA402" name="ExposureMode">0</tag>
-        <tag id="0xA403" name="WhithBalance">0</tag>
-        <tag id="0xA406" name="SceneCaptureType">0</tag>
-    </ifd>
-    <ifd name="ifd1">
-        <tag id="0x103" name="Compression">6</tag>
-        <tag id="0x11A" name="XResolution">72/1</tag>
-        <tag id="0x11B" name="YResolution">72/1</tag>
-        <tag id="0x128" name="ResolutionUnit">2</tag>
-        <tag id="0x100" name="ImageWidth">320</tag>
-        <tag id="0x101" name="ImageHeight">240</tag>
-        <tag id="0x112" name="Orientation">1</tag>
-        <tag id="0x201" name="JpegInterchangeFormat">690</tag>
-        <tag id="0x202" name="JpegInterchangeFormatLength">10447</tag>
-    </ifd>
+    <tag ifd="IFD0" id="0x0100" name="ImageWidth">2560</tag>
+    <tag ifd="IFD0" id="0x0101" name="ImageHeight">1920</tag>
+    <tag ifd="IFD0" id="0x010f" name="Make">google</tag>
+    <tag ifd="IFD0" id="0x0110" name="Model">Nexus S</tag>
+    <tag ifd="IFD0" id="0x0112" name="Orientation">1</tag>
+    <tag ifd="IFD0" id="0x0131" name="Software">MASTER</tag>
+    <tag ifd="IFD0" id="0x0132" name="ModifyDate">2012:07:30 16:28:42</tag>
+    <tag ifd="IFD0" id="0x0213" name="YCbCrPositioning">1</tag>
+    <tag ifd="IFD0" id="0x8769" name="ExifOffset">NO_VALUE</tag>
+    <tag ifd="ExifIFD" id="0x829a" name="ExposureTime">1/40</tag>
+    <tag ifd="ExifIFD" id="0x829d" name="FNumber">26/10</tag>
+    <tag ifd="ExifIFD" id="0x8822" name="ExposureProgram">3</tag>
+    <tag ifd="ExifIFD" id="0x8827" name="ISO">100</tag>
+    <tag ifd="ExifIFD" id="0x9000" name="ExifVersion">0220</tag>
+    <tag ifd="ExifIFD" id="0x9003" name="DateTimeOriginal">2012:07:30 16:28:42</tag>
+    <tag ifd="ExifIFD" id="0x9004" name="CreateDate">2012:07:30 16:28:42</tag>
+    <tag ifd="ExifIFD" id="0x9201" name="ShutterSpeedValue">50/10</tag>
+    <tag ifd="ExifIFD" id="0x9202" name="ApertureValue">30/10</tag>
+    <tag ifd="ExifIFD" id="0x9203" name="BrightnessValue">30/10</tag>
+    <tag ifd="ExifIFD" id="0x9204" name="ExposureCompensation">0/0</tag>
+    <tag ifd="ExifIFD" id="0x9205" name="MaxApertureValue">30/10</tag>
+    <tag ifd="ExifIFD" id="0x9207" name="MeteringMode">2</tag>
+    <tag ifd="ExifIFD" id="0x9209" name="Flash">0</tag>
+    <tag ifd="ExifIFD" id="0x920a" name="FocalLength">343/100</tag>
+    <tag ifd="ExifIFD" id="0x9286" name="UserComment">IICSAUser comments</tag>
+    <tag ifd="ExifIFD" id="0xa001" name="ColorSpace">1</tag>
+    <tag ifd="ExifIFD" id="0xa002" name="ExifImageWidth">2560</tag>
+    <tag ifd="ExifIFD" id="0xa003" name="ExifImageHeight">1920</tag>
+    <tag ifd="ExifIFD" id="0xa402" name="ExposureMode">0</tag>
+    <tag ifd="ExifIFD" id="0xa403" name="WhiteBalance">0</tag>
+    <tag ifd="ExifIFD" id="0xa406" name="SceneCaptureType">0</tag>
+    <tag ifd="IFD1" id="0x0100" name="ImageWidth">320</tag>
+    <tag ifd="IFD1" id="0x0101" name="ImageHeight">240</tag>
+    <tag ifd="IFD1" id="0x0103" name="Compression">6</tag>
+    <tag ifd="IFD1" id="0x0112" name="Orientation">1</tag>
+    <tag ifd="IFD1" id="0x011a" name="XResolution">72/1</tag>
+    <tag ifd="IFD1" id="0x011b" name="YResolution">72/1</tag>
+    <tag ifd="IFD1" id="0x0128" name="ResolutionUnit">2</tag>
+    <tag ifd="IFD1" id="0x0201" name="ThumbnailOffset">690</tag>
+    <tag ifd="IFD1" id="0x0202" name="ThumbnailLength">10447</tag>
 </exif>
diff --git a/tests/src/com/android/gallery3d/CameraTestRunner.java b/tests/src/com/android/gallery3d/CameraTestRunner.java
new file mode 100755
index 0000000..5032336
--- /dev/null
+++ b/tests/src/com/android/gallery3d/CameraTestRunner.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2012 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.gallery3d;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import com.android.gallery3d.functional.CameraTest;
+import com.android.gallery3d.functional.ImageCaptureIntentTest;
+import com.android.gallery3d.functional.VideoCaptureIntentTest;
+import com.android.gallery3d.unittest.CameraUnitTest;
+
+import junit.framework.TestSuite;
+
+
+public class CameraTestRunner extends InstrumentationTestRunner {
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(CameraTest.class);
+        suite.addTestSuite(ImageCaptureIntentTest.class);
+        suite.addTestSuite(VideoCaptureIntentTest.class);
+        suite.addTestSuite(CameraUnitTest.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return CameraTestRunner.class.getClassLoader();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/StressTests.java b/tests/src/com/android/gallery3d/StressTests.java
new file mode 100755
index 0000000..b991e9e
--- /dev/null
+++ b/tests/src/com/android/gallery3d/StressTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d;
+
+import com.android.gallery3d.stress.CameraLatency;
+import com.android.gallery3d.stress.CameraStartUp;
+import com.android.gallery3d.stress.ImageCapture;
+import com.android.gallery3d.stress.SwitchPreview;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * Instrumentation Test Runner for all Camera tests.
+ *
+ * Running all tests:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.gallery3d.StressTests \
+ *    -w com.google.android.gallery3d.tests/com.android.gallery3d.stress.CameraStressTestRunner
+ */
+
+public class StressTests extends TestSuite {
+    public static Test suite() {
+        TestSuite result = new TestSuite();
+        result.addTestSuite(CameraLatency.class);
+        result.addTestSuite(CameraStartUp.class);
+        result.addTestSuite(ImageCapture.class);
+//      result.addTestSuite(SwitchPreview.class);
+        return result;
+    }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifDataTest.java b/tests/src/com/android/gallery3d/exif/ExifDataTest.java
index ba656bf..949f22c 100644
--- a/tests/src/com/android/gallery3d/exif/ExifDataTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifDataTest.java
@@ -19,36 +19,135 @@
 import junit.framework.TestCase;
 
 import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class ExifDataTest extends TestCase {
+    Map<Integer, ExifTag> mTestTags;
+    ExifInterface mInterface;
+    private ExifTag mVersionTag;
+    private ExifTag mGpsVersionTag;
+    private ExifTag mModelTag;
+    private ExifTag mDateTimeTag;
+    private ExifTag mCompressionTag;
+    private ExifTag mThumbnailFormatTag;
+    private ExifTag mLongitudeTag;
+    private ExifTag mShutterTag;
+    private ExifTag mInteropIndex;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mInterface = new ExifInterface();
+
+        // TYPE_UNDEFINED with 4 components
+        mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+                5, 4, 3, 2
+        });
+        // TYPE_UNSIGNED_BYTE with 4 components
+        mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+                6, 7, 8, 9
+        });
+        // TYPE ASCII with arbitrary length
+        mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+        // TYPE_ASCII with 20 components
+        mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+        // TYPE_UNSIGNED_SHORT with 1 components
+        mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+        // TYPE_UNSIGNED_LONG with 1 components
+        mThumbnailFormatTag =
+                mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+        // TYPE_UNSIGNED_RATIONAL with 3 components
+        mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+                new Rational(2, 2), new Rational(11, 11),
+                new Rational(102, 102)
+        });
+        // TYPE_RATIONAL with 1 components
+        mShutterTag = mInterface
+                .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+        // TYPE_ASCII with arbitrary length
+        mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo");
+
+        mTestTags = new HashMap<Integer, ExifTag>();
+
+        mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+        mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+        mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+        mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+        mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+        mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+        mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+        mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+        mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mInterface = null;
+        mTestTags = null;
+    }
+
     public void testAddTag() {
         ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN);
-        // IFD0 tag
-        exifData.addTag(ExifTag.TAG_MAKE).setValue("test");
-        exifData.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(1000);
 
-        // EXIF tag
-        exifData.addTag(ExifTag.TAG_ISO_SPEED_RATINGS).setValue(1);
+        // Add all test tags
+        for (ExifTag t : mTestTags.values()) {
+            assertTrue(exifData.addTag(t) == null);
+        }
 
-        // GPS tag
-        exifData.addTag(ExifTag.TAG_GPS_ALTITUDE).setValue(new Rational(10, 100));
+        // Make sure no initial thumbnails
+        assertFalse(exifData.hasCompressedThumbnail());
+        assertFalse(exifData.hasUncompressedStrip());
 
-        // Interoperability tag
-        exifData.addInteroperabilityTag(ExifTag.TAG_INTEROPERABILITY_INDEX).setValue("inter_test");
+        // Check that we can set thumbnails
+        exifData.setStripBytes(3, new byte[] {
+                1, 2, 3, 4, 5
+        });
+        assertTrue(exifData.hasUncompressedStrip());
+        exifData.setCompressedThumbnail(new byte[] {
+            1
+        });
+        assertTrue(exifData.hasCompressedThumbnail());
 
-        // IFD1 tag
-        exifData.addThumbnailTag(ExifTag.TAG_MAKE).setValue("test_thumb");
-        exifData.addThumbnailTag(ExifTag.TAG_IMAGE_WIDTH).setValue(100);
+        // Check that we can clear thumbnails
+        exifData.clearThumbnailAndStrips();
+        assertFalse(exifData.hasCompressedThumbnail());
+        assertFalse(exifData.hasUncompressedStrip());
 
-        // check data
-        assertEquals("test", exifData.getTag(ExifTag.TAG_MAKE).getString());
-        assertEquals(1000, exifData.getTag(ExifTag.TAG_IMAGE_WIDTH).getUnsignedLong(0));
-        assertEquals(1, exifData.getTag(ExifTag.TAG_ISO_SPEED_RATINGS).getUnsignedShort(0));
-        assertEquals(new Rational(10, 100),
-                exifData.getTag(ExifTag.TAG_GPS_ALTITUDE).getRational(0));
-        assertEquals("inter_test",
-                exifData.getInteroperabilityTag(ExifTag.TAG_INTEROPERABILITY_INDEX).getString());
-        assertEquals("test_thumb", exifData.getThumbnailTag(ExifTag.TAG_MAKE).getString());
-        assertEquals(100, exifData.getThumbnailTag(ExifTag.TAG_IMAGE_WIDTH).getUnsignedLong(0));
+        // Make sure ifds exist
+        for (int i : IfdData.getIfds()) {
+            assertTrue(exifData.getIfdData(i) != null);
+        }
+
+        // Get all test tags
+        List<ExifTag> allTags = exifData.getAllTags();
+        assertTrue(allTags != null);
+
+        // Make sure all test tags are in data
+        for (ExifTag t : mTestTags.values()) {
+            boolean check = false;
+            for (ExifTag i : allTags) {
+                if (t.equals(i)) {
+                    check = true;
+                    break;
+                }
+            }
+            assertTrue(check);
+        }
+
+        // Check if getting tags for a tid works
+        List<ExifTag> tidTags = exifData.getAllTagsForTagId(ExifInterface
+                .getTrueTagKey(ExifInterface.TAG_SHUTTER_SPEED_VALUE));
+        assertTrue(tidTags.size() == 1);
+        assertTrue(tidTags.get(0).equals(mShutterTag));
+
+        // Check if getting tags for an ifd works
+        List<ExifTag> ifdTags = exifData.getAllTagsForIfd(IfdId.TYPE_IFD_INTEROPERABILITY);
+        assertTrue(ifdTags.size() == 1);
+        assertTrue(ifdTags.get(0).equals(mInteropIndex));
+
     }
 }
diff --git a/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java
new file mode 100644
index 0000000..af1ccfb
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifInterfaceTest.java
@@ -0,0 +1,527 @@
+/*
+ * 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.gallery3d.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
+import java.io.ByteArrayInputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifInterfaceTest extends ExifXmlDataTestCase {
+
+    private File mTmpFile;
+    private List<Map<Short, List<String>>> mGroundTruth;
+    private ExifInterface mInterface;
+    private ExifTag mVersionTag;
+    private ExifTag mGpsVersionTag;
+    private ExifTag mModelTag;
+    private ExifTag mDateTimeTag;
+    private ExifTag mCompressionTag;
+    private ExifTag mThumbnailFormatTag;
+    private ExifTag mLongitudeTag;
+    private ExifTag mShutterTag;
+    Map<Integer, ExifTag> mTestTags;
+    Map<Integer, Integer> mTagDefinitions;
+
+    public ExifInterfaceTest(int imageRes, int xmlRes) {
+        super(imageRes, xmlRes);
+    }
+
+    public ExifInterfaceTest(String imagePath, String xmlPath) {
+        super(imagePath, xmlPath);
+    }
+
+    public void testInterface() throws Exception {
+
+        InputStream imageInputStream = null;
+        try {
+            // Basic checks
+
+            // Check if bitmap is valid
+            byte[] imgData = Util.readToByteArray(getImageInputStream());
+            imageInputStream = new ByteArrayInputStream(imgData);
+            checkBitmap(imageInputStream);
+
+            // Check defines
+            int tag = ExifInterface.defineTag(1, (short) 0x0100);
+            assertTrue(getImageTitle(), tag == 0x00010100);
+            int tagDef = mInterface.getTagDefinition((short) 0x0100, IfdId.TYPE_IFD_0);
+            assertTrue(getImageTitle(), tagDef == 0x03040001);
+            int[] allowed = ExifInterface.getAllowedIfdsFromInfo(mInterface.getTagInfo().get(
+                    ExifInterface.TAG_IMAGE_WIDTH));
+            assertTrue(getImageTitle(), allowed.length == 2 && allowed[0] == IfdId.TYPE_IFD_0
+                    && allowed[1] == IfdId.TYPE_IFD_1);
+
+            // Check if there are any initial tags
+            assertTrue(getImageTitle(), mInterface.getAllTags() == null);
+
+            // ///////// Basic read/write testing
+
+            // Make sure we can read
+            imageInputStream = new ByteArrayInputStream(imgData);
+            mInterface.readExif(imageInputStream);
+
+            // Check tags against ground truth
+            checkTagsAgainstXml(mInterface.getAllTags());
+
+            // Make sure clearing Exif works
+            mInterface.clearExif();
+            assertTrue(getImageTitle(), mInterface.getAllTags() == null);
+
+            // Make sure setting tags works
+            mInterface.setTags(mTestTags.values());
+            checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+            // Try writing over bitmap exif
+            ByteArrayOutputStream imgModified = new ByteArrayOutputStream();
+            mInterface.writeExif(imgData, imgModified);
+
+            // Check if bitmap is valid
+            byte[] imgData2 = imgModified.toByteArray();
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            checkBitmap(imageInputStream);
+
+            // Make sure we get the same tags out
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            mInterface.readExif(imageInputStream);
+            checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+            // Reread original image
+            imageInputStream = new ByteArrayInputStream(imgData);
+            mInterface.readExif(imageInputStream);
+
+            // Write out with original exif
+            imgModified = new ByteArrayOutputStream();
+            mInterface.writeExif(imgData2, imgModified);
+
+            // Read back in exif and check tags
+            imgData2 = imgModified.toByteArray();
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            mInterface.readExif(imageInputStream);
+            checkTagsAgainstXml(mInterface.getAllTags());
+
+            // Check if bitmap is valid
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            checkBitmap(imageInputStream);
+
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        } finally {
+            Util.closeSilently(imageInputStream);
+        }
+    }
+
+    public void testInterfaceModify() throws Exception {
+
+        // TODO: This test is dependent on galaxy_nexus jpeg/xml file.
+        InputStream imageInputStream = null;
+        try {
+            // Check if bitmap is valid
+            byte[] imgData = Util.readToByteArray(getImageInputStream());
+            imageInputStream = new ByteArrayInputStream(imgData);
+            checkBitmap(imageInputStream);
+
+            // ///////// Exif modifier testing.
+
+            // Read exif and write to temp file
+            imageInputStream = new ByteArrayInputStream(imgData);
+            mInterface.readExif(imageInputStream);
+            mInterface.writeExif(imgData, mTmpFile.getPath());
+
+            // Check if bitmap is valid
+            imageInputStream = new FileInputStream(mTmpFile);
+            checkBitmap(imageInputStream);
+
+            // Create some tags to overwrite with
+            ArrayList<ExifTag> tags = new ArrayList<ExifTag>();
+            tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+                    ExifInterface.Orientation.RIGHT_TOP));
+            tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "goooooooooooooooooogle"));
+
+            // Attempt to rewrite tags
+            assertTrue(getImageTitle(), mInterface.rewriteExif(mTmpFile.getPath(), tags));
+
+            imageInputStream.close();
+            // Check if bitmap is valid
+            imageInputStream = new FileInputStream(mTmpFile);
+            checkBitmap(imageInputStream);
+
+            // Read tags and check against xml
+            mInterface.readExif(mTmpFile.getPath());
+            for (ExifTag t : mInterface.getAllTags()) {
+                short tid = t.getTagId();
+                if (tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_ORIENTATION)
+                        && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) {
+                    checkTagAgainstXml(t);
+                }
+            }
+            assertTrue(getImageTitle(), mInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION)
+                    .shortValue() == ExifInterface.Orientation.RIGHT_TOP);
+            String valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT);
+            assertTrue(getImageTitle(), valString.equals("goooooooooooooooooogle"));
+
+            // Test forced modify
+
+            // Create some tags to overwrite with
+            tags = new ArrayList<ExifTag>();
+            tags.add(mInterface.buildTag(ExifInterface.TAG_SOFTWARE, "magic super photomaker pro"));
+            tags.add(mInterface.buildTag(ExifInterface.TAG_USER_COMMENT, "noodles"));
+            tags.add(mInterface.buildTag(ExifInterface.TAG_ORIENTATION,
+                    ExifInterface.Orientation.TOP_LEFT));
+
+            // Force rewrite tags
+            mInterface.forceRewriteExif(mTmpFile.getPath(), tags);
+
+            imageInputStream.close();
+            // Check if bitmap is valid
+            imageInputStream = new FileInputStream(mTmpFile);
+            checkBitmap(imageInputStream);
+
+            // Read tags and check against xml
+            mInterface.readExif(mTmpFile.getPath());
+            for (ExifTag t : mInterface.getAllTags()) {
+                short tid = t.getTagId();
+                if (!ExifInterface.isOffsetTag(tid)
+                        && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_SOFTWARE)
+                        && tid != ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) {
+                    checkTagAgainstXml(t);
+                }
+            }
+            valString = mInterface.getTagStringValue(ExifInterface.TAG_SOFTWARE);
+            String compareString = "magic super photomaker pro\0";
+            assertTrue(getImageTitle(), valString.equals(compareString));
+            valString = mInterface.getTagStringValue(ExifInterface.TAG_USER_COMMENT);
+            assertTrue(getImageTitle(), valString.equals("noodles"));
+
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        } finally {
+            Util.closeSilently(imageInputStream);
+        }
+    }
+
+    public void testInterfaceDefines() throws Exception {
+
+        InputStream imageInputStream = null;
+        try {
+            // Check if bitmap is valid
+            byte[] imgData = Util.readToByteArray(getImageInputStream());
+            imageInputStream = new ByteArrayInputStream(imgData);
+            checkBitmap(imageInputStream);
+
+            // Set some tags.
+            mInterface.setTags(mTestTags.values());
+
+            // Check tag definitions against default
+            for (Integer i : mTestTags.keySet()) {
+                int check = mTagDefinitions.get(i).intValue();
+                int actual = mInterface.getTagInfo().get(i);
+                assertTrue(check == actual);
+            }
+
+            // Check defines
+            int tag1 = ExifInterface.defineTag(IfdId.TYPE_IFD_1, (short) 42);
+            int tag2 = ExifInterface.defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 43);
+            assertTrue(tag1 == 0x0001002a);
+            assertTrue(tag2 == 0x0003002b);
+
+            // Define some non-standard tags
+            assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+                    ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+                        IfdId.TYPE_IFD_1
+                    }) == tag1);
+            assertTrue(mInterface.getTagInfo().get(tag1) == 0x02010010);
+            assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_INTEROPERABILITY,
+                    ExifTag.TYPE_ASCII, (short) 5, new int[] {
+                            IfdId.TYPE_IFD_GPS, IfdId.TYPE_IFD_INTEROPERABILITY
+                    }) == tag2);
+            assertTrue(mInterface.getTagInfo().get(tag2) == 0x18020005);
+
+            // Make sure these don't work
+            assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+                    ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+                        IfdId.TYPE_IFD_0
+                    }) == ExifInterface.TAG_NULL);
+            assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1, (short) 0,
+                    (short) 16, new int[] {
+                        IfdId.TYPE_IFD_1
+                    }) == ExifInterface.TAG_NULL);
+            assertTrue(mInterface.setTagDefinition((short) 42, 5, ExifTag.TYPE_UNSIGNED_BYTE,
+                    (short) 16, new int[] {
+                        5
+                    }) == ExifInterface.TAG_NULL);
+            assertTrue(mInterface.setTagDefinition((short) 42, IfdId.TYPE_IFD_1,
+                    ExifTag.TYPE_UNSIGNED_BYTE, (short) 16, new int[] {
+                        -1
+                    }) == ExifInterface.TAG_NULL);
+            assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_GPS,
+                    ExifTag.TYPE_ASCII, (short) 5, new int[] {
+                        IfdId.TYPE_IFD_GPS
+                    }) == ExifInterface.TAG_NULL);
+            assertTrue(mInterface.setTagDefinition((short) 43, IfdId.TYPE_IFD_0,
+                    ExifTag.TYPE_ASCII, (short) 5, new int[] {
+                            IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_GPS
+                    }) == ExifInterface.TAG_NULL);
+
+            // Set some tags
+            mInterface.setTags(mTestTags.values());
+            checkTagsAgainstHash(mInterface.getAllTags(), mTestTags);
+
+            // Make some tags using new defines
+            ExifTag defTag0 = mInterface.buildTag(tag1, new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+            });
+            assertTrue(defTag0 != null);
+            ExifTag defTag1 = mInterface.buildTag(tag2, "hihi");
+            assertTrue(defTag1 != null);
+            ExifTag defTag2 = mInterface.buildTag(tag2, IfdId.TYPE_IFD_GPS, "byte");
+            assertTrue(defTag2 != null);
+
+            // Make sure these don't work
+            ExifTag badTag = mInterface.buildTag(tag1, new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+            });
+            assertTrue(badTag == null);
+            badTag = mInterface.buildTag(tag1, IfdId.TYPE_IFD_0, new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+            });
+            assertTrue(badTag == null);
+            badTag = mInterface.buildTag(0x0002002a, new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+            });
+            assertTrue(badTag == null);
+            badTag = mInterface.buildTag(tag2, new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17
+            });
+            assertTrue(badTag == null);
+
+            // Set the tags
+            assertTrue(mInterface.setTag(defTag0) == null);
+            assertTrue(mInterface.setTag(defTag1) == null);
+            assertTrue(mInterface.setTag(defTag2) == null);
+            assertTrue(mInterface.setTag(defTag0).equals(defTag0));
+            assertTrue(mInterface.setTag(null) == null);
+            assertTrue(mInterface.setTagValue(tag2, "yoyo") == true);
+            assertTrue(mInterface.setTagValue(tag2, "yaaarggg") == false);
+            assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0"));
+
+            // Try writing over bitmap exif
+            ByteArrayOutputStream imgModified = new ByteArrayOutputStream();
+            mInterface.writeExif(imgData, imgModified);
+
+            // Check if bitmap is valid
+            byte[] imgData2 = imgModified.toByteArray();
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            checkBitmap(imageInputStream);
+
+            // Read back in the tags
+            mInterface.readExif(imgData2);
+
+            // Check tags
+            for (ExifTag t : mInterface.getAllTags()) {
+                int tid = t.getTagId();
+                if (tid != ExifInterface.getTrueTagKey(tag1)
+                        && tid != ExifInterface.getTrueTagKey(tag2)) {
+                    checkTagAgainstHash(t, mTestTags);
+                }
+            }
+            assertTrue(Arrays.equals(mInterface.getTagByteValues(tag1), new byte[] {
+                    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+            }));
+            assertTrue(mInterface.getTagStringValue(tag2).equals("yoyo\0"));
+            assertTrue(mInterface.getTagStringValue(tag2, IfdId.TYPE_IFD_GPS).equals("byte\0"));
+
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        } finally {
+            Util.closeSilently(imageInputStream);
+        }
+    }
+
+    public void testInterfaceThumbnails() throws Exception {
+
+        InputStream imageInputStream = null;
+        try {
+            // Check if bitmap is valid
+            byte[] imgData = Util.readToByteArray(getImageInputStream());
+            imageInputStream = new ByteArrayInputStream(imgData);
+            checkBitmap(imageInputStream);
+
+            // Check thumbnails
+            mInterface.readExif(imgData);
+            Bitmap bmap = mInterface.getThumbnailBitmap();
+            assertTrue(getImageTitle(), bmap != null);
+
+            // Make a new thumbnail and set it
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inSampleSize = 16;
+            Bitmap thumb = BitmapFactory.decodeByteArray(imgData, 0, imgData.length, opts);
+            assertTrue(getImageTitle(), thumb != null);
+            assertTrue(getImageTitle(), mInterface.setCompressedThumbnail(thumb) == true);
+
+            // Write out image
+            ByteArrayOutputStream outData = new ByteArrayOutputStream();
+            mInterface.writeExif(imgData, outData);
+
+            // Make sure bitmap is still valid
+            byte[] imgData2 = outData.toByteArray();
+            imageInputStream = new ByteArrayInputStream(imgData2);
+            checkBitmap(imageInputStream);
+
+            // Read in bitmap and make sure thumbnail is still valid
+            mInterface.readExif(imgData2);
+            bmap = mInterface.getThumbnailBitmap();
+            assertTrue(getImageTitle(), bmap != null);
+
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        } finally {
+            Util.closeSilently(imageInputStream);
+        }
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mTmpFile = File.createTempFile("exif_test", ".jpg");
+        mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+
+        mInterface = new ExifInterface();
+
+        // TYPE_UNDEFINED with 4 components
+        mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+                5, 4, 3, 2
+        });
+        // TYPE_UNSIGNED_BYTE with 4 components
+        mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+                6, 7, 8, 9
+        });
+        // TYPE ASCII with arbitary length
+        mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+        // TYPE_ASCII with 20 components
+        mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+        // TYPE_UNSIGNED_SHORT with 1 components
+        mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+        // TYPE_UNSIGNED_LONG with 1 components
+        mThumbnailFormatTag =
+                mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+        // TYPE_UNSIGNED_RATIONAL with 3 components
+        mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+                new Rational(2, 2), new Rational(11, 11),
+                new Rational(102, 102)
+        });
+        // TYPE_RATIONAL with 1 components
+        mShutterTag = mInterface
+                .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+
+        mTestTags = new HashMap<Integer, ExifTag>();
+
+        mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+        mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+        mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+        mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+        mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+        mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+        mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+        mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+
+        mTagDefinitions = new HashMap<Integer, Integer>();
+        mTagDefinitions.put(ExifInterface.TAG_EXIF_VERSION, 0x04070004);
+        mTagDefinitions.put(ExifInterface.TAG_GPS_VERSION_ID, 0x10010004);
+        mTagDefinitions.put(ExifInterface.TAG_MODEL, 0x03020000);
+        mTagDefinitions.put(ExifInterface.TAG_DATE_TIME, 0x03020014);
+        mTagDefinitions.put(ExifInterface.TAG_COMPRESSION, 0x03030001);
+        mTagDefinitions.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 0x02040001);
+        mTagDefinitions.put(ExifInterface.TAG_GPS_LONGITUDE, 0x100a0003);
+        mTagDefinitions.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, 0x040a0001);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mTmpFile.delete();
+    }
+
+    // Helper functions
+
+    private void checkTagAgainstXml(ExifTag tag) {
+        List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+
+        if (truth == null) {
+            fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle());
+        }
+
+        // No value from exiftool.
+        if (truth.contains(null))
+            return;
+
+        String dataString = Util.tagValueToString(tag).trim();
+        assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle()
+                + ": " + dataString,
+                truth.contains(dataString));
+    }
+
+    private void checkTagsAgainstXml(List<ExifTag> tags) {
+        for (ExifTag t : tags) {
+            checkTagAgainstXml(t);
+        }
+    }
+
+    private void checkTagAgainstHash(ExifTag tag, Map<Integer, ExifTag> testTags) {
+        int tagdef = mInterface.getTagDefinitionForTag(tag);
+        assertTrue(getImageTitle(), tagdef != ExifInterface.TAG_NULL);
+        ExifTag t = testTags.get(tagdef);
+        // Ignore offset tags & other special tags
+        if (!ExifInterface.sBannedDefines.contains(tag.getTagId())) {
+            assertTrue(getImageTitle(), t != null);
+        } else {
+            return;
+        }
+        if (t == tag)
+            return;
+        assertTrue(getImageTitle(), tag.equals(t));
+        assertTrue(getImageTitle(), tag.getDataType() == t.getDataType());
+        assertTrue(getImageTitle(), tag.getTagId() == t.getTagId());
+        assertTrue(getImageTitle(), tag.getIfd() == t.getIfd());
+        assertTrue(getImageTitle(), tag.getComponentCount() == t.getComponentCount());
+    }
+
+    private void checkTagsAgainstHash(List<ExifTag> tags, Map<Integer, ExifTag> testTags) {
+        for (ExifTag t : tags) {
+            checkTagAgainstHash(t, testTags);
+        }
+    }
+
+    private void checkBitmap(InputStream inputStream) throws IOException {
+        Bitmap bmp = BitmapFactory.decodeStream(inputStream);
+        assertTrue(getImageTitle(), bmp != null);
+    }
+
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifModifierTest.java b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java
new file mode 100644
index 0000000..713a9d9
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/ExifModifierTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ExifModifierTest extends ExifXmlDataTestCase {
+
+    private File mTmpFile;
+    private List<Map<Short, List<String>>> mGroundTruth;
+    private ExifInterface mInterface;
+    private Map<Short, ExifTag> mTestTags;
+    ExifTag mVersionTag;
+    ExifTag mGpsVersionTag;
+    ExifTag mModelTag;
+    ExifTag mDateTimeTag;
+    ExifTag mCompressionTag;
+    ExifTag mThumbnailFormatTag;
+    ExifTag mLongitudeTag;
+    ExifTag mShutterTag;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mGroundTruth = ExifXmlReader.readXml(getXmlParser());
+        mTmpFile = File.createTempFile("exif_test", ".jpg");
+        FileOutputStream os = null;
+        InputStream is = getImageInputStream();
+        try {
+            os = new FileOutputStream(mTmpFile);
+            byte[] buf = new byte[1024];
+            int n;
+            while ((n = is.read(buf)) > 0) {
+                os.write(buf, 0, n);
+            }
+        } finally {
+            Util.closeSilently(os);
+        }
+
+        // TYPE_UNDEFINED with 4 components
+        mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+                1, 2, 3, 4
+        });
+        // TYPE_UNSIGNED_BYTE with 4 components
+        mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+                4, 3, 2, 1
+        });
+        // TYPE ASCII with arbitary length
+        mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "end-of-the-world");
+        // TYPE_ASCII with 20 components
+        mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2012:12:31 23:59:59");
+        // TYPE_UNSIGNED_SHORT with 1 components
+        mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
+        // TYPE_UNSIGNED_LONG with 1 components
+        mThumbnailFormatTag =
+                mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+        // TYPE_UNSIGNED_RATIONAL with 3 components
+        mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+                new Rational(1, 1), new Rational(10, 10),
+                new Rational(100, 100)
+        });
+        // TYPE_RATIONAL with 1 components
+        mShutterTag = mInterface
+                .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(1, 1));
+
+        mTestTags = new HashMap<Short, ExifTag>();
+
+        mTestTags.put(mVersionTag.getTagId(), mVersionTag);
+        mTestTags.put(mGpsVersionTag.getTagId(), mGpsVersionTag);
+        mTestTags.put(mModelTag.getTagId(), mModelTag);
+        mTestTags.put(mDateTimeTag.getTagId(), mDateTimeTag);
+        mTestTags.put(mCompressionTag.getTagId(), mCompressionTag);
+        mTestTags.put(mThumbnailFormatTag.getTagId(), mThumbnailFormatTag);
+        mTestTags.put(mLongitudeTag.getTagId(), mLongitudeTag);
+        mTestTags.put(mShutterTag.getTagId(), mShutterTag);
+    }
+
+    public ExifModifierTest(int imageRes, int xmlRes) {
+        super(imageRes, xmlRes);
+        mInterface = new ExifInterface();
+    }
+
+    public ExifModifierTest(String imagePath, String xmlPath) {
+        super(imagePath, xmlPath);
+        mInterface = new ExifInterface();
+    }
+
+    public void testModify() throws Exception {
+        Map<Short, Boolean> results = new HashMap<Short, Boolean>();
+
+        RandomAccessFile file = null;
+        try {
+            file = new RandomAccessFile(mTmpFile, "rw");
+            MappedByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, file.length());
+            for (ExifTag tag : mTestTags.values()) {
+                ExifModifier modifier = new ExifModifier(buf, mInterface);
+                modifier.modifyTag(tag);
+                boolean result = modifier.commit();
+                results.put(tag.getTagId(), result);
+                buf.force();
+                buf.position(0);
+
+                if (!result) {
+                    List<String> value = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+                    assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+                            value == null || tag.getTagId() == ExifInterface.TAG_MODEL);
+                }
+            }
+        } finally {
+            Util.closeSilently(file);
+        }
+
+        // Parse the new file and check the result
+        InputStream is = null;
+        try {
+            is = new FileInputStream(mTmpFile);
+            ExifData data = new ExifReader(mInterface).read(is);
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                checkIfd(data.getIfdData(i), mGroundTruth.get(i), results);
+            }
+        } finally {
+            Util.closeSilently(is);
+        }
+
+    }
+
+    private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue,
+            Map<Short, Boolean> results) {
+        if (ifd == null) {
+            assertEquals(getImageTitle(), 0, ifdValue.size());
+            return;
+        }
+        ExifTag[] tags = ifd.getAllTags();
+        for (ExifTag tag : tags) {
+            List<String> truth = ifdValue.get(tag.getTagId());
+            assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth);
+            if (truth.contains(null)) {
+                continue;
+            }
+
+            ExifTag newTag = mTestTags.get(tag.getTagId());
+            if (newTag != null
+                    && results.get(tag.getTagId())) {
+                assertEquals(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+                        Util.tagValueToString(newTag), Util.tagValueToString(tag));
+            } else {
+                assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+                        truth.contains(Util.tagValueToString(tag).trim()));
+            }
+        }
+        assertEquals(getImageTitle(), ifdValue.size(), tags.length);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mTmpFile.delete();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
index ad603df..1286c58 100644
--- a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
@@ -18,56 +18,177 @@
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.util.Log;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 
 public class ExifOutputStreamTest extends ExifXmlDataTestCase {
-    public ExifOutputStreamTest(int imageResourceId, int xmlResourceId) {
-        super(imageResourceId, xmlResourceId);
+
+    private File mTmpFile;
+
+    private ExifInterface mInterface;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mTmpFile = File.createTempFile("exif_test", ".jpg");
     }
 
-    public void testExifOutputStream() throws IOException, ExifInvalidFormatException {
-        File file = File.createTempFile("exif_test", ".jpg");
+    public ExifOutputStreamTest(int imgRes, int xmlRes) {
+        super(imgRes, xmlRes);
+        mInterface = new ExifInterface();
+    }
+
+    public ExifOutputStreamTest(String imgPath, String xmlPath) {
+        super(imgPath, xmlPath);
+        mInterface = new ExifInterface();
+    }
+
+    public void testExifOutputStream() throws Exception {
         InputStream imageInputStream = null;
         InputStream exifInputStream = null;
         FileInputStream reDecodeInputStream = null;
         FileInputStream reParseInputStream = null;
+
+        InputStream dangerInputStream = null;
+        OutputStream dangerOutputStream = null;
         try {
-            // Read the image
-            imageInputStream = getInstrumentation()
-                    .getContext().getResources().openRawResource(mImageResourceId);
-            Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+            try {
+                byte[] imgData = Util.readToByteArray(getImageInputStream());
+                imageInputStream = new ByteArrayInputStream(imgData);
+                exifInputStream = new ByteArrayInputStream(imgData);
 
-            // Read exif data
-            exifInputStream = getInstrumentation()
-                    .getContext().getResources().openRawResource(mImageResourceId);
-            ExifData exifData = new ExifReader().read(exifInputStream);
+                // Read the image data
+                Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+                // The image is invalid
+                if (bmp == null) {
+                    return;
+                }
 
-            // Encode the image with the exif data
-            FileOutputStream outputStream = new FileOutputStream(file);
-            ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream);
-            exifOutputStream.setExifData(exifData);
-            bmp.compress(Bitmap.CompressFormat.JPEG, 100, exifOutputStream);
-            exifOutputStream.close();
+                // Read exif data
+                ExifData exifData = new ExifReader(mInterface).read(exifInputStream);
 
-            // Re-decode the temp file and check the data.
-            reDecodeInputStream = new FileInputStream(file);
-            Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream);
-            assertNotNull(decodedBmp);
+                // Encode the image with the exif data
+                FileOutputStream outputStream = new FileOutputStream(mTmpFile);
+                ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream, mInterface);
+                exifOutputStream.setExifData(exifData);
+                bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream);
+                exifOutputStream.close();
+                exifOutputStream = null;
 
-            // Re-parse the temp file the check EXIF tag
-            reParseInputStream = new FileInputStream(file);
-            ExifData reExifData = new ExifReader().read(reParseInputStream);
-            assertEquals(exifData, reExifData);
-        } finally {
-            Util.closeSilently(imageInputStream);
-            Util.closeSilently(exifInputStream);
-            Util.closeSilently(reDecodeInputStream);
-            Util.closeSilently(reParseInputStream);
+                // Re-decode the temp file and check the data.
+                reDecodeInputStream = new FileInputStream(mTmpFile);
+                Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream);
+                assertNotNull(getImageTitle(), decodedBmp);
+                reDecodeInputStream.close();
+
+                // Re-parse the temp file the check EXIF tag
+                reParseInputStream = new FileInputStream(mTmpFile);
+                ExifData reExifData = new ExifReader(mInterface).read(reParseInputStream);
+                assertEquals(getImageTitle(), exifData, reExifData);
+                reParseInputStream.close();
+
+                // Try writing exif to file with existing exif.
+                dangerOutputStream = (OutputStream) new FileOutputStream(mTmpFile);
+                exifOutputStream = new ExifOutputStream(dangerOutputStream, mInterface);
+                exifOutputStream.setExifData(exifData);
+                exifOutputStream.write(imgData);
+                // exifOutputStream.write(strippedImgData);
+                exifOutputStream.close();
+                exifOutputStream = null;
+
+                // Make sure it still can be parsed into a bitmap.
+                dangerInputStream = (InputStream) new FileInputStream(mTmpFile);
+                decodedBmp = null;
+                decodedBmp = BitmapFactory.decodeStream(dangerInputStream);
+                assertNotNull(getImageTitle(), decodedBmp);
+                dangerInputStream.close();
+                dangerInputStream = null;
+
+                // Make sure exif is still well-formatted.
+                dangerInputStream = (InputStream) new FileInputStream(mTmpFile);
+                reExifData = null;
+                reExifData = new ExifReader(mInterface).read(dangerInputStream);
+                assertEquals(getImageTitle(), exifData, reExifData);
+                dangerInputStream.close();
+                dangerInputStream = null;
+
+            } finally {
+                Util.closeSilently(imageInputStream);
+                Util.closeSilently(exifInputStream);
+                Util.closeSilently(reDecodeInputStream);
+                Util.closeSilently(reParseInputStream);
+
+                Util.closeSilently(dangerInputStream);
+                Util.closeSilently(dangerOutputStream);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
         }
     }
-}
\ No newline at end of file
+
+    public void testOutputSpeed() throws Exception {
+        final String LOGTAG = "testOutputSpeed";
+        InputStream imageInputStream = null;
+        OutputStream imageOutputStream = null;
+        try {
+            try {
+                imageInputStream = getImageInputStream();
+                // Read the image data
+                Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+                // The image is invalid
+                if (bmp == null) {
+                    return;
+                }
+                imageInputStream.close();
+                int nLoops = 20;
+                long totalReadDuration = 0;
+                long totalWriteDuration = 0;
+                for (int i = 0; i < nLoops; i++) {
+                    imageInputStream = reopenFileStream();
+                    // Read exif data
+                    long startTime = System.nanoTime();
+                    ExifData exifData = new ExifReader(mInterface).read(imageInputStream);
+                    long endTime = System.nanoTime();
+                    long duration = endTime - startTime;
+                    totalReadDuration += duration;
+                    Log.v(LOGTAG, " read time: " + duration);
+                    imageInputStream.close();
+
+                    // Encode the image with the exif data
+                    imageOutputStream = (OutputStream) new FileOutputStream(mTmpFile);
+                    ExifOutputStream exifOutputStream = new ExifOutputStream(imageOutputStream,
+                            mInterface);
+                    exifOutputStream.setExifData(exifData);
+                    startTime = System.nanoTime();
+                    bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream);
+                    endTime = System.nanoTime();
+                    duration = endTime - startTime;
+                    totalWriteDuration += duration;
+                    Log.v(LOGTAG, " write time: " + duration);
+                    exifOutputStream.close();
+                }
+                Log.v(LOGTAG, "======================= normal");
+                Log.v(LOGTAG, "avg read time: " + totalReadDuration / nLoops);
+                Log.v(LOGTAG, "avg write time: " + totalWriteDuration / nLoops);
+                Log.v(LOGTAG, "=======================");
+            } finally {
+                Util.closeSilently(imageInputStream);
+                Util.closeSilently(imageOutputStream);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mTmpFile.delete();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
index c1ad834..5c3fd24 100644
--- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java
@@ -16,219 +16,219 @@
 
 package com.android.gallery3d.exif;
 
-import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class ExifParserTest extends ExifXmlDataTestCase {
     private static final String TAG = "ExifParserTest";
 
-    private HashMap<Short, String> mIfd0Value = new HashMap<Short, String>();
-    private HashMap<Short, String> mIfd1Value = new HashMap<Short, String>();
-    private HashMap<Short, String> mExifIfdValue = new HashMap<Short, String>();
-    private HashMap<Short, String> mInteroperabilityIfdValue = new HashMap<Short, String>();
+    private ExifInterface mInterface;
 
-    private InputStream mImageInputStream;
-
-    public ExifParserTest(int imageResourceId, int xmlResourceId) {
-        super(imageResourceId, xmlResourceId);
+    public ExifParserTest(int imgRes, int xmlRes) {
+        super(imgRes, xmlRes);
+        mInterface = new ExifInterface();
     }
 
+    public ExifParserTest(String imgPath, String xmlPath) {
+        super(imgPath, xmlPath);
+        mInterface = new ExifInterface();
+    }
+
+    private List<Map<Short, List<String>>> mGroundTruth;
+
     @Override
-    protected void setUp() throws Exception {
-        mImageInputStream = getInstrumentation()
-                .getContext().getResources().openRawResource(mImageResourceId);
-
-        XmlResourceParser parser =
-                getInstrumentation().getContext().getResources().getXml(mXmlResourceId);
-
-        ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue
-                , mInteroperabilityIfdValue);
-        parser.close();
+    public void setUp() throws Exception {
+        super.setUp();
+        mGroundTruth = ExifXmlReader.readXml(getXmlParser());
     }
 
-    public void testParse() throws IOException, ExifInvalidFormatException {
-        ExifParser parser = ExifParser.parse(mImageInputStream);
-        int event = parser.next();
-        while (event != ExifParser.EVENT_END) {
-            switch (event) {
-                case ExifParser.EVENT_START_OF_IFD:
-                    break;
-                case ExifParser.EVENT_NEW_TAG:
-                    ExifTag tag = parser.getTag();
-                    if (!tag.hasValue()) {
-                        parser.registerForTagValue(tag);
-                    } else {
+    public void testParse() throws Exception {
+        try {
+            ExifParser parser = ExifParser.parse(getImageInputStream(), mInterface);
+            int event = parser.next();
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag tag = parser.getTag();
+                        if (!tag.hasValue()) {
+                            parser.registerForTagValue(tag);
+                        } else {
+                            checkTag(tag);
+                        }
+                        break;
+                    case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                        tag = parser.getTag();
+                        if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                            byte[] buf = new byte[tag.getComponentCount()];
+                            parser.read(buf);
+                            assertTrue(TAG, tag.setValue(buf));
+                        }
                         checkTag(tag);
-                    }
-                    break;
-                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
-                    tag = parser.getTag();
-                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
-                        byte[] buf = new byte[tag.getComponentCount()];
-                        parser.read(buf);
-                        tag.setValue(buf);
-                    }
-                    checkTag(tag);
-                    break;
+                        break;
+                }
+                event = parser.next();
             }
-            event = parser.next();
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
         }
     }
 
     private void checkTag(ExifTag tag) {
-        HashMap<Short, String> truth = null;
-        switch (tag.getIfd()) {
-            case IfdId.TYPE_IFD_0:
-                truth = mIfd0Value;
-                break;
-            case IfdId.TYPE_IFD_1:
-                truth = mIfd1Value;
-                break;
-            case IfdId.TYPE_IFD_EXIF:
-                truth = mExifIfdValue;
-                break;
-            case IfdId.TYPE_IFD_INTEROPERABILITY:
-                truth = mInteroperabilityIfdValue;
-                break;
+        List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId());
+
+        if (truth == null) {
+            fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle());
         }
 
-        String truthString = truth.get(tag.getTagId());
-        String dataString = tag.valueToString().trim();
-        if (truthString == null) {
-            fail(String.format("Unknown Tag %02x", tag.getTagId()));
+        // No value from exiftool.
+        if (truth.contains(null)) {
+            return;
         }
-        assertEquals(String.format("Tag %02x", tag.getTagId()), truthString, dataString);
+
+        String dataString = Util.tagValueToString(tag).trim();
+        assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle()
+                + ": " + dataString,
+                truth.contains(dataString));
     }
 
-    private void parseOneIfd(int ifd, int options, HashMap<Short, String> expectedResult)
-            throws IOException, ExifInvalidFormatException {
-        int numOfTag = 0;
-        ExifParser parser = ExifParser.parse(mImageInputStream, options);
-        int event = parser.next();
-        while(event != ExifParser.EVENT_END) {
-            switch (event) {
-                case ExifParser.EVENT_START_OF_IFD:
-                    assertEquals(ifd, parser.getCurrentIfd());
-                    break;
-                case ExifParser.EVENT_NEW_TAG:
-                    numOfTag++;
-                    ExifTag tag = parser.getTag();
-                    if (tag.hasValue()) {
-                        checkTag(tag);
-                    } else {
-                        parser.registerForTagValue(tag);
-                    }
-                    break;
-                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
-                    tag = parser.getTag();
-                    if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
-                        byte[] buf = new byte[tag.getComponentCount()];
-                        parser.read(buf);
-                        tag.setValue(buf);
-                    }
-                    checkTag(tag);
-                    break;
-                case ExifParser.EVENT_COMPRESSED_IMAGE:
-                case ExifParser.EVENT_UNCOMPRESSED_STRIP:
-                    fail("Invalid Event type: " + event);
-                    break;
-            }
-            event = parser.next();
-        }
-        assertEquals(expectedResult.size(), numOfTag);
-    }
-
-    public void testOnlyExifIfd() throws IOException, ExifInvalidFormatException {
-        parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF, mExifIfdValue);
-    }
-
-    public void testOnlyIfd0() throws IOException, ExifInvalidFormatException {
-        parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0, mIfd0Value);
-    }
-
-    public void testOnlyIfd1() throws IOException, ExifInvalidFormatException {
-        parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1, mIfd1Value);
-    }
-
-    public void testOnlyInteroperabilityIfd() throws IOException, ExifInvalidFormatException {
-        parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY
-                , mInteroperabilityIfdValue);
-    }
-
-    public void testOnlyReadSomeTag() throws IOException, ExifInvalidFormatException {
-        ExifParser parser = ExifParser.parse(mImageInputStream, ExifParser.OPTION_IFD_0);
-        int event = parser.next();
-        boolean isTagFound = false;
-        while (event != ExifParser.EVENT_END) {
-            switch (event) {
-                case ExifParser.EVENT_START_OF_IFD:
-                    assertEquals(IfdId.TYPE_IFD_0, parser.getCurrentIfd());
-                    break;
-                case ExifParser.EVENT_NEW_TAG:
-                    ExifTag tag = parser.getTag();
-                    if (tag.getTagId() == ExifTag.TAG_MODEL) {
+    private void parseOneIfd(int ifd, int options) throws Exception {
+        try {
+            Map<Short, List<String>> expectedResult = mGroundTruth.get(ifd);
+            int numOfTag = 0;
+            ExifParser parser = ExifParser.parse(getImageInputStream(), options, mInterface);
+            int event = parser.next();
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        assertEquals(getImageTitle(), ifd, parser.getCurrentIfd());
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag tag = parser.getTag();
+                        numOfTag++;
                         if (tag.hasValue()) {
-                            isTagFound = true;
                             checkTag(tag);
                         } else {
                             parser.registerForTagValue(tag);
                         }
-                        parser.skipRemainingTagsInCurrentIfd();
-                    }
-                    break;
-                case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
-                    tag = parser.getTag();
-                    assertEquals(ExifTag.TAG_MODEL, tag.getTagId());
-                    checkTag(tag);
-                    isTagFound = true;
-                    break;
-            }
-            event = parser.next();
-        }
-        assertTrue(isTagFound);
-    }
-
-    public void testReadThumbnail() throws ExifInvalidFormatException, IOException {
-        ExifParser parser = ExifParser.parse(mImageInputStream,
-                ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL);
-
-        int event = parser.next();
-        Bitmap bmp = null;
-        boolean mIsContainCompressedImage = false;
-        while (event != ExifParser.EVENT_END) {
-            switch (event) {
-                case ExifParser.EVENT_NEW_TAG:
-                    ExifTag tag = parser.getTag();
-                    if (tag.getTagId() == ExifTag.TAG_COMPRESSION) {
-                        if (tag.getUnsignedShort(0) == ExifTag.Compression.JPEG) {
-                            mIsContainCompressedImage = true;
+                        break;
+                    case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                        tag = parser.getTag();
+                        if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
+                            byte[] buf = new byte[tag.getComponentCount()];
+                            parser.read(buf);
+                            tag.setValue(buf);
                         }
-                    }
-                    break;
-                case ExifParser.EVENT_COMPRESSED_IMAGE:
-                    int imageSize = parser.getCompressedImageSize();
-                    byte buf[] = new byte[imageSize];
-                    parser.read(buf);
-                    bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize);
-                    break;
+                        checkTag(tag);
+                        break;
+                    case ExifParser.EVENT_COMPRESSED_IMAGE:
+                    case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+                        fail("Invalid Event type: " + event + ", " + getImageTitle());
+                        break;
+                }
+                event = parser.next();
             }
-            event = parser.next();
-        }
-        if (mIsContainCompressedImage) {
-            assertNotNull(bmp);
+            assertEquals(getImageTitle(), ExifXmlReader.getTrueTagNumber(expectedResult), numOfTag);
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
         }
     }
 
-    @Override
-    protected void tearDown() throws IOException {
-        mImageInputStream.close();
-        mIfd0Value.clear();
-        mIfd1Value.clear();
-        mExifIfdValue.clear();
+    public void testOnlyExifIfd() throws Exception {
+        parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF);
     }
-}
\ No newline at end of file
+
+    public void testOnlyIfd0() throws Exception {
+        parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0);
+    }
+
+    public void testOnlyIfd1() throws Exception {
+        parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1);
+    }
+
+    public void testOnlyInteroperabilityIfd() throws Exception {
+        parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY);
+    }
+
+    public void testOnlyReadSomeTag() throws Exception {
+        // Do not do this test if there is no model tag.
+        if (mGroundTruth.get(IfdId.TYPE_IFD_0).get(ExifInterface.TAG_MODEL) == null) {
+            return;
+        }
+
+        try {
+            ExifParser parser = ExifParser.parse(getImageInputStream(), ExifParser.OPTION_IFD_0,
+                    mInterface);
+            int event = parser.next();
+            boolean isTagFound = false;
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_START_OF_IFD:
+                        assertEquals(getImageTitle(), IfdId.TYPE_IFD_0, parser.getCurrentIfd());
+                        break;
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag tag = parser.getTag();
+                        if (tag.getTagId() == ExifInterface.TAG_MODEL) {
+                            if (tag.hasValue()) {
+                                isTagFound = true;
+                                checkTag(tag);
+                            } else {
+                                parser.registerForTagValue(tag);
+                            }
+                            parser.skipRemainingTagsInCurrentIfd();
+                        }
+                        break;
+                    case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+                        tag = parser.getTag();
+                        assertEquals(getImageTitle(), ExifInterface.TAG_MODEL, tag.getTagId());
+                        checkTag(tag);
+                        isTagFound = true;
+                        break;
+                }
+                event = parser.next();
+            }
+            assertTrue(getImageTitle(), isTagFound);
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
+
+    public void testReadThumbnail() throws Exception {
+        try {
+            ExifParser parser = ExifParser.parse(getImageInputStream(),
+                    ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL, mInterface);
+
+            int event = parser.next();
+            Bitmap bmp = null;
+            boolean mIsContainCompressedImage = false;
+            while (event != ExifParser.EVENT_END) {
+                switch (event) {
+                    case ExifParser.EVENT_NEW_TAG:
+                        ExifTag tag = parser.getTag();
+                        if (tag.getTagId() == ExifInterface.TAG_COMPRESSION) {
+                            if (tag.getValueAt(0) == ExifInterface.Compression.JPEG) {
+                                mIsContainCompressedImage = true;
+                            }
+                        }
+                        break;
+                    case ExifParser.EVENT_COMPRESSED_IMAGE:
+                        int imageSize = parser.getCompressedImageSize();
+                        byte buf[] = new byte[imageSize];
+                        parser.read(buf);
+                        bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize);
+                        break;
+                }
+                event = parser.next();
+            }
+            if (mIsContainCompressedImage) {
+                assertNotNull(getImageTitle(), bmp);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
+}
diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
index 2691208..a057186 100644
--- a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java
@@ -16,119 +16,147 @@
 
 package com.android.gallery3d.exif;
 
-import android.content.res.XmlResourceParser;
 import android.graphics.BitmapFactory;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class ExifReaderTest extends ExifXmlDataTestCase {
     private static final String TAG = "ExifReaderTest";
 
-    private final HashMap<Short, String> mIfd0Value = new HashMap<Short, String>();
-    private final HashMap<Short, String> mIfd1Value = new HashMap<Short, String>();
-    private final HashMap<Short, String> mExifIfdValue = new HashMap<Short, String>();
-    private final HashMap<Short, String> mInteroperabilityIfdValue = new HashMap<Short, String>();
-
-    private InputStream mImageInputStream;
-
-    public ExifReaderTest(int imageResourceId, int xmlResourceId) {
-        super(imageResourceId, xmlResourceId);
-    }
+    private ExifInterface mInterface;
+    private List<Map<Short, List<String>>> mGroundTruth;
 
     @Override
     public void setUp() throws Exception {
-        mImageInputStream = getInstrumentation()
-                .getContext().getResources().openRawResource(mImageResourceId);
-
-        XmlResourceParser parser =
-                getInstrumentation().getContext().getResources().getXml(mXmlResourceId);
-
-        ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue
-                , mInteroperabilityIfdValue);
-        parser.close();
+        super.setUp();
+        mGroundTruth = ExifXmlReader.readXml(getXmlParser());
     }
 
-    public void testRead() throws ExifInvalidFormatException, IOException {
-        ExifReader reader = new ExifReader();
-        ExifData exifData = reader.read(mImageInputStream);
-        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_0), mIfd0Value);
-        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_1), mIfd1Value);
-        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_EXIF), mExifIfdValue);
-        checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
-                mInteroperabilityIfdValue);
-        checkThumbnail(exifData);
+    public ExifReaderTest(int imgRes, int xmlRes) {
+        super(imgRes, xmlRes);
+        mInterface = new ExifInterface();
+    }
+
+    public ExifReaderTest(String imgPath, String xmlPath) {
+        super(imgPath, xmlPath);
+        mInterface = new ExifInterface();
+    }
+
+    public void testRead() throws Exception {
+        try {
+            ExifReader reader = new ExifReader(mInterface);
+            ExifData exifData = reader.read(getImageInputStream());
+            for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+                checkIfd(exifData.getIfdData(i), mGroundTruth.get(i));
+            }
+            checkThumbnail(exifData);
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
     }
 
     private void checkThumbnail(ExifData exifData) {
+        Map<Short, List<String>> ifd1Truth = mGroundTruth.get(IfdId.TYPE_IFD_1);
+
+        List<String> typeTagValue = ifd1Truth.get(ExifInterface.TAG_COMPRESSION);
+        if (typeTagValue == null)
+            return;
+
         IfdData ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1);
-        if (ifd1 != null) {
-            if (ifd1.getTag(ExifTag.TAG_COMPRESSION).getUnsignedShort(0) ==
-                    ExifTag.Compression.JPEG) {
-                assertTrue(exifData.hasCompressedThumbnail());
-                byte[] thumbnail = exifData.getCompressedThumbnail();
-                assertTrue(BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null);
+        if (ifd1 == null)
+            fail(getImageTitle() + ": failed to find IFD1");
+
+        String typeTagTruth = typeTagValue.get(0);
+
+        int type = (int) ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPRESSION))
+                .getValueAt(0);
+
+        if (String.valueOf(ExifInterface.Compression.JPEG).equals(typeTagTruth)) {
+            assertTrue(getImageTitle(), type == ExifInterface.Compression.JPEG);
+            assertTrue(getImageTitle(), exifData.hasCompressedThumbnail());
+            byte[] thumbnail = exifData.getCompressedThumbnail();
+            assertTrue(getImageTitle(),
+                    BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null);
+        } else if (String.valueOf(ExifInterface.Compression.UNCOMPRESSION).equals(typeTagTruth)) {
+            assertTrue(getImageTitle(), type == ExifInterface.Compression.UNCOMPRESSION);
+            // Try to check the strip count with the formula provided by EXIF spec.
+            int planarType = ExifInterface.PlanarConfiguration.CHUNKY;
+            ExifTag planarTag = ifd1.getTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_PLANAR_CONFIGURATION));
+            if (planarTag != null) {
+                planarType = (int) planarTag.getValueAt(0);
+            }
+
+            if (!ifd1Truth.containsKey(ExifInterface.TAG_IMAGE_LENGTH) ||
+                    !ifd1Truth.containsKey(ExifInterface.TAG_ROWS_PER_STRIP)) {
+                return;
+            }
+
+            ExifTag heightTag = ifd1.getTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_IMAGE_LENGTH));
+            ExifTag rowPerStripTag = ifd1.getTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_ROWS_PER_STRIP));
+
+            // Fail the test if required tags are missing
+            if (heightTag == null || rowPerStripTag == null) {
+                fail(getImageTitle());
+            }
+
+            int imageLength = (int) heightTag.getValueAt(0);
+            int rowsPerStrip = (int) rowPerStripTag.getValueAt(0);
+            int stripCount = ifd1.getTag(
+                    ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
+                    .getComponentCount();
+
+            if (planarType == ExifInterface.PlanarConfiguration.CHUNKY) {
+                assertTrue(getImageTitle(),
+                        stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip);
             } else {
-                // Try to check the strip count with the formula provided by EXIF spec.
-                int planarType = ExifTag.PlanarConfiguration.CHUNKY;
-                ExifTag planarTag = ifd1.getTag(ExifTag.TAG_PLANAR_CONFIGURATION);
-                if (planarTag != null) {
-                    planarType = planarTag.getUnsignedShort(0);
+                if (!ifd1Truth.containsKey(ExifInterface.TAG_SAMPLES_PER_PIXEL)) {
+                    return;
                 }
+                ExifTag samplePerPixelTag = ifd1.getTag(ExifInterface
+                        .getTrueTagKey(ExifInterface.TAG_SAMPLES_PER_PIXEL));
+                int samplePerPixel = (int) samplePerPixelTag.getValueAt(0);
+                assertTrue(getImageTitle(),
+                        stripCount ==
+                        (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel);
+            }
 
-                ExifTag heightTag = ifd1.getTag(ExifTag.TAG_IMAGE_LENGTH);
-                ExifTag rowPerStripTag = ifd1.getTag(ExifTag.TAG_ROWS_PER_STRIP);
-
-                int imageLength = getUnsignedIntOrShort(heightTag);
-                int rowsPerStrip = getUnsignedIntOrShort(rowPerStripTag);
-                int stripCount = ifd1.getTag(
-                        ExifTag.TAG_STRIP_OFFSETS).getComponentCount();
-
-                if (planarType == ExifTag.PlanarConfiguration.CHUNKY) {
-                    assertTrue(stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip);
+            if (!ifd1Truth.containsKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)) {
+                return;
+            }
+            ExifTag byteCountTag = ifd1.getTag(ExifInterface
+                    .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+            short byteCountDataType = byteCountTag.getDataType();
+            for (int i = 0; i < stripCount; i++) {
+                if (byteCountDataType == ExifTag.TYPE_UNSIGNED_SHORT) {
+                    assertEquals(getImageTitle(),
+                            byteCountTag.getValueAt(i), exifData.getStrip(i).length);
                 } else {
-                    ExifTag samplePerPixelTag = ifd1.getTag(ExifTag.TAG_SAMPLES_PER_PIXEL);
-                    int samplePerPixel = samplePerPixelTag.getUnsignedShort(0);
-                    assertTrue(stripCount ==
-                            (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel);
-                }
-
-                for (int i = 0; i < stripCount; i++) {
-                    ExifTag byteCountTag = ifd1.getTag(ExifTag.TAG_STRIP_BYTE_COUNTS);
-                    if (byteCountTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-                        assertEquals(byteCountTag.getUnsignedShort(i), exifData.getStrip(i).length);
-                    } else {
-                        assertEquals(
-                                byteCountTag.getUnsignedLong(i), exifData.getStrip(i).length);
-                    }
+                    assertEquals(getImageTitle(),
+                            byteCountTag.getValueAt(i), exifData.getStrip(i).length);
                 }
             }
         }
     }
 
-    private int getUnsignedIntOrShort(ExifTag tag) {
-        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
-            return tag.getUnsignedShort(0);
-        } else {
-            return (int) tag.getUnsignedLong(0);
-        }
-    }
-
-    private void checkIfd(IfdData ifd, HashMap<Short, String> ifdValue) {
+    private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue) {
         if (ifd == null) {
-            assertEquals(0 ,ifdValue.size());
+            assertEquals(getImageTitle(), 0, ifdValue.size());
             return;
         }
         ExifTag[] tags = ifd.getAllTags();
         for (ExifTag tag : tags) {
-            assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString().trim());
+            List<String> truth = ifdValue.get(tag.getTagId());
+            assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth);
+            if (truth.contains(null)) {
+                continue;
+            }
+            assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(),
+                    truth.contains(Util.tagValueToString(tag).trim()));
         }
-        assertEquals(ifdValue.size(), tags.length);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mImageInputStream.close();
+        assertEquals(getImageTitle(), ifdValue.size(), tags.length);
     }
 }
diff --git a/tests/src/com/android/gallery3d/exif/ExifTagTest.java b/tests/src/com/android/gallery3d/exif/ExifTagTest.java
index 128956d..59067c3 100644
--- a/tests/src/com/android/gallery3d/exif/ExifTagTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifTagTest.java
@@ -18,27 +18,83 @@
 
 import junit.framework.TestCase;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class ExifTagTest extends TestCase {
 
     private static long MAX_UNSIGNED_LONG = (1L << 32) - 1;
     private static int MAX_LONG = Integer.MAX_VALUE;
     private static int MIN_LONG = Integer.MIN_VALUE;
 
-    private static final ExifTag sTestTags[] = {
-        ExifTag.buildTag(ExifTag.TAG_EXIF_VERSION), // TYPE_UNDEFINED with 4 components
-        ExifTag.buildTag(ExifTag.TAG_GPS_VERSION_ID), // TYPE_UNSIGNED_BYTE with 4 components
-        ExifTag.buildTag(ExifTag.TAG_DATE_TIME), // TYPE_ASCII with 20 components
-        ExifTag.buildTag(ExifTag.TAG_COMPRESSION), // TYPE_UNSIGNED_SHORT with 1 components
+    Map<Integer, ExifTag> mTestTags;
+    ExifInterface mInterface;
+    private ExifTag mVersionTag;
+    private ExifTag mGpsVersionTag;
+    private ExifTag mModelTag;
+    private ExifTag mDateTimeTag;
+    private ExifTag mCompressionTag;
+    private ExifTag mThumbnailFormatTag;
+    private ExifTag mLongitudeTag;
+    private ExifTag mShutterTag;
+    private ExifTag mInteropIndex;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mInterface = new ExifInterface();
+
+        // TYPE_UNDEFINED with 4 components
+        mVersionTag = mInterface.buildTag(ExifInterface.TAG_EXIF_VERSION, new byte[] {
+                5, 4, 3, 2
+        });
+        // TYPE_UNSIGNED_BYTE with 4 components
+        mGpsVersionTag = mInterface.buildTag(ExifInterface.TAG_GPS_VERSION_ID, new byte[] {
+                6, 7, 8, 9
+        });
+        // TYPE ASCII with arbitrary length
+        mModelTag = mInterface.buildTag(ExifInterface.TAG_MODEL, "helloworld");
+        // TYPE_ASCII with 20 components
+        mDateTimeTag = mInterface.buildTag(ExifInterface.TAG_DATE_TIME, "2013:02:11 20:20:20");
+        // TYPE_UNSIGNED_SHORT with 1 components
+        mCompressionTag = mInterface.buildTag(ExifInterface.TAG_COMPRESSION, 100);
         // TYPE_UNSIGNED_LONG with 1 components
-        ExifTag.buildTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT),
-        ExifTag.buildTag(ExifTag.TAG_GPS_LONGITUDE), // TYPE_UNSIGNED_RATIONAL with 3 components
-        ExifTag.buildTag(ExifTag.TAG_SHUTTER_SPEED_VALUE), // TYPE_RATIONAL with 1 components
-        // There is no tag defined with TYPE_LONG. Create a dummy one for testing.
-        new ExifTag((short) 0, ExifTag.TYPE_LONG, 1, 0)
-    };
+        mThumbnailFormatTag =
+                mInterface.buildTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, 100);
+        // TYPE_UNSIGNED_RATIONAL with 3 components
+        mLongitudeTag = mInterface.buildTag(ExifInterface.TAG_GPS_LONGITUDE, new Rational[] {
+                new Rational(2, 2), new Rational(11, 11),
+                new Rational(102, 102)
+        });
+        // TYPE_RATIONAL with 1 components
+        mShutterTag = mInterface
+                .buildTag(ExifInterface.TAG_SHUTTER_SPEED_VALUE, new Rational(4, 6));
+        // TYPE_ASCII with arbitrary length
+        mInteropIndex = mInterface.buildTag(ExifInterface.TAG_INTEROPERABILITY_INDEX, "foo");
+
+        mTestTags = new HashMap<Integer, ExifTag>();
+
+        mTestTags.put(ExifInterface.TAG_EXIF_VERSION, mVersionTag);
+        mTestTags.put(ExifInterface.TAG_GPS_VERSION_ID, mGpsVersionTag);
+        mTestTags.put(ExifInterface.TAG_MODEL, mModelTag);
+        mTestTags.put(ExifInterface.TAG_DATE_TIME, mDateTimeTag);
+        mTestTags.put(ExifInterface.TAG_COMPRESSION, mCompressionTag);
+        mTestTags.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, mThumbnailFormatTag);
+        mTestTags.put(ExifInterface.TAG_GPS_LONGITUDE, mLongitudeTag);
+        mTestTags.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, mShutterTag);
+        mTestTags.put(ExifInterface.TAG_INTEROPERABILITY_INDEX, mInteropIndex);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mInterface = null;
+        mTestTags = null;
+    }
 
     public void testValueType() {
-        for (ExifTag tag: sTestTags) {
+        for (ExifTag tag : mTestTags.values()) {
+            assertTrue(tag != null);
             int count = tag.getComponentCount();
             int intBuf[] = new int[count];
             long longBuf[] = new long[count];
@@ -51,7 +107,11 @@
                 byteBuf[i] = 0;
                 rationalBuf[i] = new Rational(0, 0);
                 // The string size should equal to component count - 1
-                if (i != 0) sb.append("*");
+                if (i != count - 1) {
+                    sb.append("*");
+                } else {
+                    sb.append("\0");
+                }
             }
             String strBuf = sb.toString();
 
@@ -66,141 +126,90 @@
     }
 
     private void checkTypeByte(ExifTag tag, byte[] buf) {
-        boolean excepThrow = false;
         short type = tag.getDataType();
-        try {
-            tag.setValue(buf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
-        assertTrue("Tag ID: " + tag.getTagId(),
-                (type == ExifTag.TYPE_UNDEFINED || type == ExifTag.TYPE_UNSIGNED_BYTE)
-                ^ excepThrow);
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(buf)
+                ^ (type == ExifTag.TYPE_UNDEFINED || type == ExifTag.TYPE_UNSIGNED_BYTE));
     }
 
     private void checkTypeAscii(ExifTag tag, String str) {
-        boolean excepThrow = false;
-        try {
-            tag.setValue(str);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
-        assertTrue("Tag ID: " + tag.getTagId(),
-                tag.getDataType() == ExifTag.TYPE_ASCII ^ excepThrow);
+        short type = tag.getDataType();
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(str)
+                ^ (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED));
     }
 
     private void checkTypeUnsignedShort(ExifTag tag, int[] intBuf) {
-        boolean excepThrow = false;
         short type = tag.getDataType();
-        try {
-            tag.setValue(intBuf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
-        assertTrue("Tag ID: " + tag.getTagId(),
-                (type == ExifTag.TYPE_UNSIGNED_SHORT
-                || type == ExifTag.TYPE_UNSIGNED_LONG || type == ExifTag.TYPE_LONG) ^ excepThrow);
+        assertFalse("\nTag: " + tag.toString(),
+                tag.setValue(intBuf)
+                        ^ (type == ExifTag.TYPE_UNSIGNED_SHORT
+                                || type == ExifTag.TYPE_UNSIGNED_LONG
+                                || type == ExifTag.TYPE_LONG));
     }
 
     private void checkTypeUnsignedLong(ExifTag tag, int[] intBuf, long[] longBuf) {
 
         // Test value only for unsigned long.
-        boolean excepThrow = false;
         int count = intBuf.length;
-        try {
-            intBuf[count - 1] = MAX_LONG;
-            tag.setValue(intBuf);
-            longBuf[count - 1] = MAX_UNSIGNED_LONG;
-            tag.setValue(longBuf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
+        intBuf[count - 1] = MAX_LONG;
+        tag.setValue(intBuf);
+        longBuf[count - 1] = MAX_UNSIGNED_LONG;
+
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(longBuf)
+                ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_LONG));
+
         intBuf[count - 1] = 0;
-        assertTrue("Tag ID: " + tag.getTagId(),
-                tag.getDataType() == ExifTag.TYPE_UNSIGNED_LONG ^ excepThrow);
-
-
         // Test invalid value for all type.
-        try {
-            longBuf[count - 1] = MAX_UNSIGNED_LONG + 1;
-            tag.setValue(longBuf);
-            fail();
-        } catch (IllegalArgumentException expected) {}
+        longBuf[count - 1] = MAX_UNSIGNED_LONG + 1;
+        assertFalse(tag.setValue(longBuf));
         longBuf[count - 1] = 0;
     }
 
     private void checkTypeLong(ExifTag tag, int[] intBuf) {
-        boolean excepThrow = false;
         int count = intBuf.length;
-        try {
-            intBuf[count - 1] = MAX_LONG;
-            tag.setValue(intBuf);
-            intBuf[count - 1] = MIN_LONG;
-            tag.setValue(intBuf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
+        intBuf[count - 1] = MAX_LONG;
+        tag.setValue(intBuf);
+        intBuf[count - 1] = MIN_LONG;
+
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(intBuf)
+                ^ (tag.getDataType() == ExifTag.TYPE_LONG));
         intBuf[count - 1] = 0;
-        assertTrue("Tag ID: " + tag.getTagId(),
-                tag.getDataType() == ExifTag.TYPE_LONG ^ excepThrow);
     }
 
     private void checkTypeRational(ExifTag tag, Rational rationalBuf[]) {
-        boolean excepThrow = false;
         int count = rationalBuf.length;
         Rational r = rationalBuf[count - 1];
-        try {
-            rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG);
-            tag.setValue(rationalBuf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
-        assertTrue("Tag ID: " + tag.getTagId(),
-                tag.getDataType() == ExifTag.TYPE_RATIONAL ^ excepThrow);
+        rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG);
 
-        if(tag.getDataType() == ExifTag.TYPE_RATIONAL) {
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf)
+                ^ (tag.getDataType() == ExifTag.TYPE_RATIONAL));
+
+        if (tag.getDataType() == ExifTag.TYPE_RATIONAL) {
             // check overflow
-            try {
-                rationalBuf[count - 1] = new Rational(MAX_LONG + 1L, MIN_LONG);
-                tag.setValue(rationalBuf);
-                fail();
-            } catch (IllegalArgumentException expected) {}
 
-            try {
-                rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG - 1L);
-                tag.setValue(rationalBuf);
-                fail();
-            } catch (IllegalArgumentException expected) {}
+            rationalBuf[count - 1] = new Rational(MAX_LONG + 1L, MIN_LONG);
+            assertFalse(tag.setValue(rationalBuf));
+
+            rationalBuf[count - 1] = new Rational(MAX_LONG, MIN_LONG - 1L);
+            assertFalse(tag.setValue(rationalBuf));
         }
         rationalBuf[count - 1] = r;
     }
 
     private void checkTypeUnsignedRational(ExifTag tag, Rational rationalBuf[]) {
-        boolean excepThrow = false;
         int count = rationalBuf.length;
         Rational r = rationalBuf[count - 1];
-        try {
-            rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, MAX_UNSIGNED_LONG);
-            tag.setValue(rationalBuf);
-        } catch (IllegalArgumentException e) {
-            excepThrow = true;
-        }
-        assertTrue("Tag ID: " + tag.getTagId(),
-                tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL ^ excepThrow);
+        rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, MAX_UNSIGNED_LONG);
 
-        if(tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL) {
+        assertFalse("\nTag: " + tag.toString(), tag.setValue(rationalBuf)
+                ^ (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL));
+
+        if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_RATIONAL) {
             // check overflow
-            try {
-                rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG + 1, 0);
-                tag.setValue(rationalBuf);
-                fail();
-            } catch (IllegalArgumentException expected) {}
+            rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG + 1, 0);
+            assertFalse(tag.setValue(rationalBuf));
 
-            try {
-                rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, -1);
-                tag.setValue(rationalBuf);
-                fail();
-            } catch (IllegalArgumentException expected) {}
+            rationalBuf[count - 1] = new Rational(MAX_UNSIGNED_LONG, -1);
+            assertFalse(tag.setValue(rationalBuf));
         }
         rationalBuf[count - 1] = r;
     }
diff --git a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
index 57a7111..162baea 100644
--- a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
+++ b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java
@@ -16,6 +16,8 @@
 
 package com.android.gallery3d.exif;
 
+import android.content.Context;
+import android.os.Environment;
 import android.test.InstrumentationTestRunner;
 import android.test.InstrumentationTestSuite;
 import android.util.Log;
@@ -25,30 +27,61 @@
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
+import java.io.File;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
 
 public class ExifTestRunner extends InstrumentationTestRunner {
     private static final String TAG = "ExifTestRunner";
 
     private static final int[] IMG_RESOURCE = {
-        R.raw.galaxy_nexus
+            R.raw.galaxy_nexus
     };
+
     private static final int[] EXIF_DATA_RESOURCE = {
-        R.xml.galaxy_nexus
+            R.xml.galaxy_nexus
     };
 
+    private static List<String> mTestImgPath = new ArrayList<String>();
+    private static List<String> mTestXmlPath = new ArrayList<String>();
+
     @Override
     public TestSuite getAllTests() {
+        getTestImagePath();
         TestSuite suite = new InstrumentationTestSuite(this);
         suite.addTestSuite(ExifDataTest.class);
         suite.addTestSuite(ExifTagTest.class);
         addAllTestsFromExifTestCase(ExifParserTest.class, suite);
         addAllTestsFromExifTestCase(ExifReaderTest.class, suite);
         addAllTestsFromExifTestCase(ExifOutputStreamTest.class, suite);
+        addAllTestsFromExifTestCase(ExifModifierTest.class, suite);
+        addAllTestsFromExifTestCase(ExifInterfaceTest.class, suite);
         return suite;
     }
 
+    private void getTestImagePath() {
+        Context context = getContext();
+        File imgDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+        File xmlDir = new File(context.getExternalFilesDir(null).getPath(), "Xml");
+
+        if (imgDir != null && xmlDir != null) {
+            String[] imgs = imgDir.list();
+            if (imgs == null) {
+                return;
+            }
+            for (String imgName : imgs) {
+                String xmlName = imgName.substring(0, imgName.lastIndexOf('.')) + ".xml";
+                File xmlFile = new File(xmlDir, xmlName);
+                if (xmlFile.exists()) {
+                    mTestImgPath.add(new File(imgDir, imgName).getAbsolutePath());
+                    mTestXmlPath.add(xmlFile.getAbsolutePath());
+                }
+            }
+        }
+    }
+
     private void addAllTestsFromExifTestCase(Class<? extends ExifXmlDataTestCase> testClass,
             TestSuite suite) {
         for (Method method : testClass.getDeclaredMethods()) {
@@ -72,6 +105,25 @@
                         Log.e(TAG, "Failed to create test case", e);
                     }
                 }
+                for (int i = 0, n = mTestImgPath.size(); i < n; i++) {
+                    TestCase test;
+                    try {
+                        test = testClass.getDeclaredConstructor(String.class, String.class).
+                                newInstance(mTestImgPath.get(i), mTestXmlPath.get(i));
+                        test.setName(method.getName());
+                        suite.addTest(test);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (InstantiationException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (IllegalAccessException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (InvocationTargetException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    } catch (NoSuchMethodException e) {
+                        Log.e(TAG, "Failed to create test case", e);
+                    }
+                }
             }
         }
     }
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
index 41b3151..da86020 100644
--- a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
@@ -14,17 +14,95 @@
  * limitations under the License.
  */
 
-
 package com.android.gallery3d.exif;
 
+import android.content.res.Resources;
 import android.test.InstrumentationTestCase;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 
 public class ExifXmlDataTestCase extends InstrumentationTestCase {
-    protected final int mImageResourceId;
-    protected final int mXmlResourceId;
+
+    private static final String RES_ID_TITLE = "Resource ID: %x";
+
+    private InputStream mImageInputStream;
+    private InputStream mXmlInputStream;
+    private XmlPullParser mXmlParser;
+    private final String mImagePath;
+    private final String mXmlPath;
+    private final int mImageResourceId;
+    private final int mXmlResourceId;
 
     public ExifXmlDataTestCase(int imageRes, int xmlRes) {
+        mImagePath = null;
+        mXmlPath = null;
         mImageResourceId = imageRes;
         mXmlResourceId = xmlRes;
     }
+
+    public ExifXmlDataTestCase(String imagePath, String xmlPath) {
+        mImagePath = imagePath;
+        mXmlPath = xmlPath;
+        mImageResourceId = 0;
+        mXmlResourceId = 0;
+    }
+
+    protected InputStream getImageInputStream() {
+        return mImageInputStream;
+    }
+
+    protected XmlPullParser getXmlParser() {
+        return mXmlParser;
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        try {
+            if (mImagePath != null) {
+                mImageInputStream = new FileInputStream(mImagePath);
+                mXmlInputStream = new FileInputStream(mXmlPath);
+                mXmlParser = Xml.newPullParser();
+                mXmlParser.setInput(new InputStreamReader(mXmlInputStream));
+            } else {
+                Resources res = getInstrumentation().getContext().getResources();
+                mImageInputStream = res.openRawResource(mImageResourceId);
+                mXmlParser = res.getXml(mXmlResourceId);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        Util.closeSilently(mImageInputStream);
+        Util.closeSilently(mXmlInputStream);
+        mXmlParser = null;
+    }
+
+    protected String getImageTitle() {
+        if (mImagePath != null) {
+            return mImagePath;
+        } else {
+            return String.format(RES_ID_TITLE, mImageResourceId);
+        }
+    }
+
+    protected InputStream reopenFileStream() throws Exception {
+        try {
+            if (mImagePath != null) {
+                return new FileInputStream(mImagePath);
+            } else {
+                Resources res = getInstrumentation().getContext().getResources();
+                return res.openRawResource(mImageResourceId);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
 }
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
index 72dd313..12e9cf7 100644
--- a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java
@@ -20,78 +20,106 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 public class ExifXmlReader {
+    private static final String TAG_EXIF = "exif";
+    private static final String TAG_TAG = "tag";
 
-    private static final String XML_EXIF_TAG = "exif";
-    private static final String XML_IFD_TAG = "ifd";
-    private static final String XML_IFD_NAME = "name";
-    private static final String XML_TAG = "tag";
-    private static final String XML_IFD0 = "ifd0";
-    private static final String XML_IFD1 = "ifd1";
-    private static final String XML_EXIF_IFD = "exif-ifd";
-    private static final String XML_INTEROPERABILITY_IFD = "interoperability-ifd";
-    private static final String XML_TAG_ID = "id";
+    private static final String IFD0 = "IFD0";
+    private static final String EXIF_IFD = "ExifIFD";
+    private static final String GPS_IFD = "GPS";
+    private static final String IFD1 = "IFD1";
+    private static final String INTEROP_IFD = "InteropIFD";
 
-    public static void readXml(XmlPullParser parser, HashMap<Short, String> ifd0,
-            HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd,
-            HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException,
-            IOException {
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_IFD = "ifd";
+
+    private static final String NO_VALUE = "NO_VALUE";
+
+    /**
+     * This function read the ground truth XML.
+     *
+     * @throws XmlPullParserException
+     * @throws IOException
+     */
+    static public List<Map<Short, List<String>>> readXml(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        List<Map<Short, List<String>>> exifData =
+                new ArrayList<Map<Short, List<String>>>(IfdId.TYPE_IFD_COUNT);
+        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+            exifData.add(new HashMap<Short, List<String>>());
+        }
 
         while (parser.next() != XmlPullParser.END_DOCUMENT) {
             if (parser.getEventType() == XmlPullParser.START_TAG) {
                 break;
             }
         }
+        parser.require(XmlPullParser.START_TAG, null, TAG_EXIF);
 
-        assert(parser.getName().equals(XML_EXIF_TAG));
-
-        parser.require(XmlPullParser.START_TAG, null, XML_EXIF_TAG);
         while (parser.next() != XmlPullParser.END_TAG) {
-            if (parser.getEventType() == XmlPullParser.START_TAG) {
-                readXmlIfd(parser, ifd0, ifd1, exifIfd, interoperabilityIfd);
+            if (parser.getEventType() != XmlPullParser.START_TAG) {
+                continue;
             }
+
+            parser.require(XmlPullParser.START_TAG, null, TAG_TAG);
+
+            int ifdId = getIfdIdFromString(parser.getAttributeValue(null, ATTR_IFD));
+            short id = Integer.decode(parser.getAttributeValue(null, ATTR_ID)).shortValue();
+
+            String value = "";
+            if (parser.next() == XmlPullParser.TEXT) {
+                value = parser.getText();
+                parser.next();
+            }
+
+            if (ifdId < 0) {
+                // TODO: the MarkerNote segment.
+            } else {
+                List<String> tagData = exifData.get(ifdId).get(id);
+                if (tagData == null) {
+                    tagData = new ArrayList<String>();
+                    exifData.get(ifdId).put(id, tagData);
+                }
+                if (NO_VALUE.equals(value)) {
+                    tagData.add(null);
+                } else {
+                    tagData.add(value.trim());
+                }
+            }
+
+            parser.require(XmlPullParser.END_TAG, null, null);
         }
-        parser.require(XmlPullParser.END_TAG, null, XML_EXIF_TAG);
+        return exifData;
     }
 
-    private static void readXmlIfd(XmlPullParser parser, HashMap<Short, String> ifd0,
-            HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd,
-            HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException,
-            IOException {
-        parser.require(XmlPullParser.START_TAG, null, XML_IFD_TAG);
-        String name = parser.getAttributeValue(null, XML_IFD_NAME);
-        HashMap<Short, String> ifdData = null;
-        if (XML_IFD0.equals(name)) {
-            ifdData = ifd0;
-        } else if (XML_IFD1.equals(name)) {
-            ifdData = ifd1;
-        } else if (XML_EXIF_IFD.equals(name)) {
-            ifdData = exifIfd;
-        } else if (XML_INTEROPERABILITY_IFD.equals(name)) {
-            ifdData = interoperabilityIfd;
+    static private int getIfdIdFromString(String prefix) {
+        if (IFD0.equals(prefix)) {
+            return IfdId.TYPE_IFD_0;
+        } else if (EXIF_IFD.equals(prefix)) {
+            return IfdId.TYPE_IFD_EXIF;
+        } else if (GPS_IFD.equals(prefix)) {
+            return IfdId.TYPE_IFD_GPS;
+        } else if (IFD1.equals(prefix)) {
+            return IfdId.TYPE_IFD_1;
+        } else if (INTEROP_IFD.equals(prefix)) {
+            return IfdId.TYPE_IFD_INTEROPERABILITY;
         } else {
-            throw new RuntimeException("Unknown IFD name in xml file: " + name);
+            assert (false);
+            return -1;
         }
-        while (parser.next() != XmlPullParser.END_TAG) {
-            if (parser.getEventType() == XmlPullParser.START_TAG) {
-                readXmlTag(parser, ifdData);
-            }
-        }
-        parser.require(XmlPullParser.END_TAG, null, XML_IFD_TAG);
     }
 
-    private static void readXmlTag(XmlPullParser parser, HashMap<Short, String> data)
-        throws XmlPullParserException, IOException {
-        parser.require(XmlPullParser.START_TAG, null, XML_TAG);
-        short id = Integer.decode(parser.getAttributeValue(null, XML_TAG_ID)).shortValue();
-        String value = "";
-        if (parser.next() == XmlPullParser.TEXT) {
-            value = parser.getText();
-            parser.next();
+    static public int getTrueTagNumber(Map<Short, List<String>> ifdData) {
+        int size = 0;
+        for (List<String> tag : ifdData.values()) {
+            size += tag.size();
         }
-        data.put(id, value);
-        parser.require(XmlPullParser.END_TAG, null, XML_TAG);
+        return size;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/src/com/android/gallery3d/exif/Util.java b/tests/src/com/android/gallery3d/exif/Util.java
new file mode 100644
index 0000000..15de007
--- /dev/null
+++ b/tests/src/com/android/gallery3d/exif/Util.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.exif;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+class Util {
+    public static boolean equals(Object a, Object b) {
+        return (a == b) || (a == null ? false : a.equals(b));
+    }
+
+    public static void closeSilently(Closeable c) {
+        if (c == null)
+            return;
+        try {
+            c.close();
+        } catch (Throwable t) {
+            // do nothing
+        }
+    }
+
+    public static byte[] readToByteArray(InputStream is) throws IOException {
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        int len;
+        byte[] buf = new byte[1024];
+        while ((len = is.read(buf)) > -1) {
+            bos.write(buf, 0, len);
+        }
+        bos.flush();
+        return bos.toByteArray();
+    }
+
+    /**
+     * Tags that are not defined in the spec.
+     */
+    static final short TAG_XP_TITLE = (short) 0x9c9b;
+    static final short TAG_XP_COMMENT = (short) 0x9c9c;
+    static final short TAG_XP_AUTHOR = (short) 0x9c9d;
+    static final short TAG_XP_KEYWORDS = (short) 0x9c9e;
+    static final short TAG_XP_SUBJECT = (short) 0x9c9f;
+
+    private static String tagUndefinedTypeValueToString(ExifTag tag) {
+        StringBuilder sbuilder = new StringBuilder();
+        byte[] buf = new byte[tag.getComponentCount()];
+        tag.getBytes(buf);
+        short tagId = tag.getTagId();
+        if (tagId == ExifInterface.getTrueTagKey(ExifInterface.TAG_COMPONENTS_CONFIGURATION)) {
+            for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                if (i != 0) {
+                    sbuilder.append(" ");
+                }
+                sbuilder.append(buf[i]);
+            }
+        } else {
+            if (buf.length == 1) {
+                sbuilder.append(buf[0]);
+            } else {
+                for (int i = 0, n = buf.length; i < n; i++) {
+                    byte code = buf[i];
+                    if (code == 0) {
+                        continue;
+                    }
+                    if (code > 31 && code < 127) {
+                        sbuilder.append((char) code);
+                    } else {
+                        sbuilder.append('.');
+                    }
+                }
+            }
+        }
+        return sbuilder.toString();
+    }
+
+    /**
+     * Returns a string representation of the value of this tag.
+     */
+    public static String tagValueToString(ExifTag tag) {
+        StringBuilder sbuilder = new StringBuilder();
+        short id = tag.getTagId();
+        switch (tag.getDataType()) {
+            case ExifTag.TYPE_UNDEFINED:
+                sbuilder.append(tagUndefinedTypeValueToString(tag));
+                break;
+            case ExifTag.TYPE_UNSIGNED_BYTE:
+                if (id == ExifInterface.TAG_MAKER_NOTE || id == TAG_XP_TITLE ||
+                        id == TAG_XP_COMMENT || id == TAG_XP_AUTHOR ||
+                        id == TAG_XP_KEYWORDS || id == TAG_XP_SUBJECT) {
+                    sbuilder.append(tagUndefinedTypeValueToString(tag));
+                } else {
+                    byte[] buf = new byte[tag.getComponentCount()];
+                    tag.getBytes(buf);
+                    for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                        if (i != 0)
+                            sbuilder.append(" ");
+                        sbuilder.append(buf[i]);
+                    }
+                }
+                break;
+            case ExifTag.TYPE_ASCII:
+                byte[] buf = tag.getStringByte();
+                for (int i = 0, n = buf.length; i < n; i++) {
+                    byte code = buf[i];
+                    if (code == 0) {
+                        // Treat some tag as undefined type data.
+                        if (id == ExifInterface.TAG_COPYRIGHT
+                                || id == ExifInterface.TAG_GPS_DATE_STAMP) {
+                            continue;
+                        } else {
+                            break;
+                        }
+                    }
+                    if (code > 31 && code < 127) {
+                        sbuilder.append((char) code);
+                    } else {
+                        sbuilder.append('.');
+                    }
+                }
+                break;
+            case ExifTag.TYPE_UNSIGNED_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    if (i != 0) {
+                        sbuilder.append(" ");
+                    }
+                    sbuilder.append(tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_RATIONAL:
+            case ExifTag.TYPE_UNSIGNED_RATIONAL:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    Rational r = tag.getRational(i);
+                    if (i != 0) {
+                        sbuilder.append(" ");
+                    }
+                    sbuilder.append(r.getNumerator()).append("/").append(r.getDenominator());
+                }
+                break;
+            case ExifTag.TYPE_UNSIGNED_SHORT:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    if (i != 0) {
+                        sbuilder.append(" ");
+                    }
+                    sbuilder.append((int) tag.getValueAt(i));
+                }
+                break;
+            case ExifTag.TYPE_LONG:
+                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+                    if (i != 0) {
+                        sbuilder.append(" ");
+                    }
+                    sbuilder.append((int) tag.getValueAt(i));
+                }
+                break;
+        }
+        return sbuilder.toString();
+    }
+
+    public static String valueToString(Object obj) {
+        if (obj instanceof int[]) {
+            return Arrays.toString((int[]) obj);
+        } else if (obj instanceof Integer[]) {
+            return Arrays.toString((Integer[]) obj);
+        } else if (obj instanceof long[]) {
+            return Arrays.toString((long[]) obj);
+        } else if (obj instanceof Long[]) {
+            return Arrays.toString((Long[]) obj);
+        } else if (obj instanceof Rational) {
+            return ((Rational) obj).toString();
+        } else if (obj instanceof Rational[]) {
+            return Arrays.toString((Rational[]) obj);
+        } else if (obj instanceof byte[]) {
+            return Arrays.toString((byte[]) obj);
+        } else if (obj != null) {
+            return obj.toString();
+        }
+        return "";
+    }
+}
diff --git a/tests/src/com/android/gallery3d/functional/CameraTest.java b/tests/src/com/android/gallery3d/functional/CameraTest.java
new file mode 100644
index 0000000..c293c0d
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/CameraTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.functional;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+public class CameraTest extends InstrumentationTestCase {
+    @LargeTest
+    public void testVideoCaptureIntentFdLeak() throws Exception {
+        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file://"
+                + Environment.getExternalStorageDirectory().toString()
+                + "test_fd_leak.3gp"));
+        getInstrumentation().startActivitySync(intent).finish();
+        // Test if the fd is closed.
+        for (File f: new File("/proc/" + Process.myPid() + "/fd").listFiles()) {
+            assertEquals(-1, f.getCanonicalPath().indexOf("test_fd_leak.3gp"));
+        }
+    }
+
+    @LargeTest
+    public void testActivityLeak() throws Exception {
+        checkActivityLeak(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+        checkActivityLeak(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+    }
+
+    private void checkActivityLeak(String action) throws Exception {
+        final int TEST_COUNT = 5;
+        Intent intent = new Intent(action);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClass(getInstrumentation().getTargetContext(),
+                CameraActivity.class);
+        ArrayList<WeakReference<Activity>> refs =
+                new ArrayList<WeakReference<Activity>>();
+        for (int i = 0; i < TEST_COUNT; i++) {
+            Activity activity = getInstrumentation().startActivitySync(intent);
+            refs.add(new WeakReference<Activity>(activity));
+            activity.finish();
+            getInstrumentation().waitForIdleSync();
+            activity = null;
+        }
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        int refCount = 0;
+        for (WeakReference<Activity> c: refs) {
+            if (c.get() != null) refCount++;
+        }
+        // If applications are leaking activity, every reference is reachable.
+        assertTrue(refCount != TEST_COUNT);
+    }
+}
diff --git a/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java b/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java
new file mode 100644
index 0000000..8d394b5
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/ImageCaptureIntentTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+public class ImageCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private Intent mIntent;
+
+    public ImageCaptureIntentTest() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+    }
+
+    @LargeTest
+    public void testNoExtraOutput() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        takePicture();
+        pressDone();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+        Intent resultData = getActivity().getResultData();
+        Bitmap bitmap = (Bitmap) resultData.getParcelableExtra("data");
+        assertNotNull(bitmap);
+        assertTrue(bitmap.getWidth() > 0);
+        assertTrue(bitmap.getHeight() > 0);
+    }
+
+    @LargeTest
+    public void testExtraOutput() throws Exception {
+        File file = new File(Environment.getExternalStorageDirectory(),
+            "test.jpg");
+        BufferedInputStream stream = null;
+        byte[] jpegData;
+
+        try {
+            mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
+            setActivityIntent(mIntent);
+            getActivity();
+
+            takePicture();
+            pressDone();
+
+            assertTrue(getActivity().isFinishing());
+            assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+
+            // Verify the jpeg file
+            int fileLength = (int) file.length();
+            assertTrue(fileLength > 0);
+            jpegData = new byte[fileLength];
+            stream = new BufferedInputStream(new FileInputStream(file));
+            stream.read(jpegData);
+        } finally {
+            if (stream != null) stream.close();
+            file.delete();
+        }
+
+        Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+        assertTrue(b.getWidth() > 0);
+        assertTrue(b.getHeight() > 0);
+    }
+
+    @LargeTest
+    public void testCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testSnapshotCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        takePicture();
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    private void takePicture() throws Exception {
+        getInstrumentation().sendKeySync(
+                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS));
+        getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+        Thread.sleep(4000);
+    }
+
+    private void pressDone() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_done).performClick();
+            }
+        });
+    }
+
+    private void pressCancel() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_cancel).performClick();
+            }
+        });
+    }
+}
diff --git a/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java b/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java
new file mode 100644
index 0000000..c8d7bbb
--- /dev/null
+++ b/tests/src/com/android/gallery3d/functional/VideoCaptureIntentTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.File;
+
+public class VideoCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private static final String TAG = "VideoCaptureIntentTest";
+    private Intent mIntent;
+    private Uri mVideoUri;
+    private File mFile, mFile2;
+
+    public VideoCaptureIntentTest() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mVideoUri != null) {
+            ContentResolver resolver = getActivity().getContentResolver();
+            Uri query = mVideoUri.buildUpon().build();
+            String[] projection = new String[] {VideoColumns.DATA};
+
+            Cursor cursor = null;
+            try {
+                cursor = resolver.query(query, projection, null, null, null);
+                if (cursor != null && cursor.moveToFirst()) {
+                    new File(cursor.getString(0)).delete();
+                }
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+
+            resolver.delete(mVideoUri, null, null);
+        }
+        if (mFile != null) mFile.delete();
+        if (mFile2 != null) mFile2.delete();
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void testNoExtraOutput() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        Intent resultData = getActivity().getResultData();
+        mVideoUri = resultData.getData();
+        assertNotNull(mVideoUri);
+        verify(getActivity(), mVideoUri);
+    }
+
+    @LargeTest
+    public void testExtraOutput() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+    }
+
+    @LargeTest
+    public void testCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testRecordCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testExtraSizeLimit() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        final long sizeLimit = 500000;  // bytes
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);  // use low quality to speed up
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo(5000);
+        pressDone();
+
+        verify(getActivity(), uri);
+        long length = mFile.length();
+        Log.v(TAG, "Video size is " + length + " bytes.");
+        assertTrue(length > 0);
+        assertTrue("Actual size=" + length, length <= sizeLimit);
+    }
+
+    @LargeTest
+    public void testExtraDurationLimit() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        final int durationLimit = 2;  // seconds
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit);
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo(5000);
+        pressDone();
+
+        int duration = verify(getActivity(), uri);
+        // The duraion should be close to to the limit. The last video duration
+        // also has duration, so the total duration may exceeds the limit a
+        // little bit.
+        Log.v(TAG, "Video length is " + duration + " ms.");
+        assertTrue(duration  < (durationLimit + 1) * 1000);
+    }
+
+    @LargeTest
+    public void testExtraVideoQuality() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        mFile2 = new File(Environment.getExternalStorageDirectory(), "video2.tmp");
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);  // low quality
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+        setActivity(null);
+
+        uri = Uri.fromFile(mFile2);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);  // high quality
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+        assertTrue(mFile.length() <= mFile2.length());
+    }
+
+    // Verify result code, result data, and the duration.
+    private int verify(CameraActivity activity, Uri uri) throws Exception {
+        assertTrue(activity.isFinishing());
+        assertEquals(Activity.RESULT_OK, activity.getResultCode());
+
+        // Verify the video file
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(activity, uri);
+        String duration = retriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertNotNull(duration);
+        int durationValue = Integer.parseInt(duration);
+        Log.v(TAG, "Video duration is " + durationValue);
+        assertTrue(durationValue > 0);
+        return durationValue;
+    }
+
+    private void recordVideo(int ms) throws Exception {
+        getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+        Thread.sleep(ms);
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                // If recording is in progress, stop it. Run these atomically in
+                // UI thread.
+                CameraActivity activity = getActivity();
+                if (activity.isRecording()) {
+                    activity.findViewById(R.id.shutter_button).performClick();
+                }
+            }
+        });
+    }
+
+    private void recordVideo() throws Exception {
+        recordVideo(2000);
+    }
+
+    private void pressDone() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_done).performClick();
+            }
+        });
+    }
+
+    private void pressCancel() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_cancel).performClick();
+            }
+        });
+    }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java
new file mode 100644
index 0000000..a57c188
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.ui.GLCanvasStub;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLCanvasMock extends GLCanvasStub {
+    // fillRect
+    int mFillRectCalled;
+    float mFillRectWidth;
+    float mFillRectHeight;
+    int mFillRectColor;
+    // drawMixed
+    int mDrawMixedCalled;
+    float mDrawMixedRatio;
+    // drawTexture;
+    int mDrawTextureCalled;
+
+    private GL11 mGL;
+
+    public GLCanvasMock(GL11 gl) {
+        mGL = gl;
+    }
+
+    public GLCanvasMock() {
+        mGL = new GLStub();
+    }
+
+    @Override
+    public GL11 getGLInstance() {
+        return mGL;
+    }
+
+    @Override
+    public void fillRect(float x, float y, float width, float height, int color) {
+        mFillRectCalled++;
+        mFillRectWidth = width;
+        mFillRectHeight = height;
+        mFillRectColor = color;
+    }
+
+    @Override
+    public void drawTexture(
+                BasicTexture texture, int x, int y, int width, int height) {
+        mDrawTextureCalled++;
+    }
+
+    @Override
+    public void drawMixed(BasicTexture from, BasicTexture to,
+            float ratio, int x, int y, int w, int h) {
+        mDrawMixedCalled++;
+        mDrawMixedRatio = ratio;
+    }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java
new file mode 100644
index 0000000..b1e6d5b
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+@SmallTest
+public class GLCanvasTest extends TestCase {
+    private static final String TAG = "GLCanvasTest";
+
+    private static GLPaint newColorPaint(int color) {
+        GLPaint paint = new GLPaint();
+        paint.setColor(color);
+        return paint;
+    }
+
+    @SmallTest
+    public void testSetSize() {
+        GL11 glStub = new GLStub();
+        GLCanvas canvas = new GLES11Canvas(glStub);
+        canvas.setSize(100, 200);
+        canvas.setSize(1000, 100);
+        try {
+            canvas.setSize(-1, 100);
+            fail();
+        } catch (Throwable ex) {
+            // expected.
+        }
+    }
+
+    @SmallTest
+    public void testClearBuffer() {
+        new ClearBufferTest().run();
+    }
+
+    private static class ClearBufferTest extends GLMock {
+        void run() {
+            GLCanvas canvas = new GLES11Canvas(this);
+            assertEquals(0, mGLClearCalled);
+            canvas.clearBuffer();
+            assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask);
+            assertEquals(1, mGLClearCalled);
+        }
+    }
+
+    @SmallTest
+    public void testSetColor() {
+        new SetColorTest().run();
+    }
+
+    // This test assumes we use pre-multipled alpha blending and should
+    // set the blending function and color correctly.
+    private static class SetColorTest extends GLMock {
+        void run() {
+            int[] testColors = new int[] {
+                0, 0xFFFFFFFF, 0xFF000000, 0x00FFFFFF, 0x80FF8001,
+                0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D
+            };
+
+            GLCanvas canvas = new GLES11Canvas(this);
+            canvas.setSize(400, 300);
+            // Test one color to make sure blend function is set.
+            assertEquals(0, mGLColorCalled);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(0x7F804020));
+            assertEquals(1, mGLColorCalled);
+            assertEquals(0x7F402010, mGLColor);
+            assertPremultipliedBlending(this);
+
+            // Test other colors to make sure premultiplication is right
+            for (int c : testColors) {
+                float a = (c >>> 24) / 255f;
+                float r = ((c >> 16) & 0xff) / 255f;
+                float g = ((c >> 8) & 0xff) / 255f;
+                float b = (c & 0xff) / 255f;
+                int pre = makeColor4f(a * r, a * g, a * b, a);
+
+                mGLColorCalled = 0;
+                canvas.drawLine(0, 0, 1, 1, newColorPaint(c));
+                assertEquals(1, mGLColorCalled);
+                assertEquals(pre, mGLColor);
+            }
+        }
+    }
+
+    @SmallTest
+    public void testSetGetMultiplyAlpha() {
+        GL11 glStub = new GLStub();
+        GLCanvas canvas = new GLES11Canvas(glStub);
+
+        canvas.setAlpha(1f);
+        assertEquals(1f, canvas.getAlpha());
+
+        canvas.setAlpha(0f);
+        assertEquals(0f, canvas.getAlpha());
+
+        canvas.setAlpha(0.5f);
+        assertEquals(0.5f, canvas.getAlpha());
+
+        canvas.multiplyAlpha(0.5f);
+        assertEquals(0.25f, canvas.getAlpha());
+
+        canvas.multiplyAlpha(0f);
+        assertEquals(0f, canvas.getAlpha());
+
+        try {
+            canvas.setAlpha(-0.01f);
+            fail();
+        } catch (Throwable ex) {
+            // expected.
+        }
+
+        try {
+            canvas.setAlpha(1.01f);
+            fail();
+        } catch (Throwable ex) {
+            // expected.
+        }
+    }
+
+    @SmallTest
+    public void testAlpha() {
+        new AlphaTest().run();
+    }
+
+    private static class AlphaTest extends GLMock {
+        void run() {
+            GLCanvas canvas = new GLES11Canvas(this);
+            canvas.setSize(400, 300);
+
+            assertEquals(0, mGLColorCalled);
+            canvas.setAlpha(0.48f);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(0xFF804020));
+            assertPremultipliedBlending(this);
+            assertEquals(1, mGLColorCalled);
+            assertEquals(0x7A3D1F0F, mGLColor);
+        }
+    }
+
+    @SmallTest
+    public void testDrawLine() {
+        new DrawLineTest().run();
+    }
+
+    // This test assumes the drawLine() function use glDrawArrays() with
+    // GL_LINE_STRIP mode to draw the line and the input coordinates are used
+    // directly.
+    private static class DrawLineTest extends GLMock {
+        private int mDrawArrayCalled = 0;
+        private final int[] mResult = new int[4];
+
+        @Override
+        public void glDrawArrays(int mode, int first, int count) {
+            assertNotNull(mGLVertexPointer);
+            assertEquals(GL10.GL_LINE_STRIP, mode);
+            assertEquals(2, count);
+            mGLVertexPointer.bindByteBuffer();
+
+            double[] coord = new double[4];
+            mGLVertexPointer.getArrayElement(first, coord);
+            mResult[0] = (int) coord[0];
+            mResult[1] = (int) coord[1];
+            mGLVertexPointer.getArrayElement(first + 1, coord);
+            mResult[2] = (int) coord[0];
+            mResult[3] = (int) coord[1];
+            mDrawArrayCalled++;
+        }
+
+        void run() {
+            GLCanvas canvas = new GLES11Canvas(this);
+            canvas.setSize(400, 300);
+            canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */);
+            assertTrue(mGLVertexArrayEnabled);
+            assertEquals(1, mDrawArrayCalled);
+
+            Log.v(TAG, "result = " + Arrays.toString(mResult));
+            int[] answer = new int[] {2, 7, 1, 8};
+            for (int i = 0; i < answer.length; i++) {
+                assertEquals(answer[i], mResult[i]);
+            }
+        }
+    }
+
+    @SmallTest
+    public void testFillRect() {
+        new FillRectTest().run();
+    }
+
+    // This test assumes the drawLine() function use glDrawArrays() with
+    // GL_TRIANGLE_STRIP mode to draw the line and the input coordinates
+    // are used directly.
+    private static class FillRectTest extends GLMock {
+        private int mDrawArrayCalled = 0;
+        private final int[] mResult = new int[8];
+
+        @Override
+        public void glDrawArrays(int mode, int first, int count) {
+            assertNotNull(mGLVertexPointer);
+            assertEquals(GL10.GL_TRIANGLE_STRIP, mode);
+            assertEquals(4, count);
+            mGLVertexPointer.bindByteBuffer();
+
+            double[] coord = new double[4];
+            for (int i = 0; i < 4; i++) {
+                mGLVertexPointer.getArrayElement(first + i, coord);
+                mResult[i * 2 + 0] = (int) coord[0];
+                mResult[i * 2 + 1] = (int) coord[1];
+            }
+
+            mDrawArrayCalled++;
+        }
+
+        void run() {
+            GLCanvas canvas = new GLES11Canvas(this);
+            canvas.setSize(400, 300);
+            canvas.fillRect(2, 7, 1, 8, 0 /* color */);
+            assertTrue(mGLVertexArrayEnabled);
+            assertEquals(1, mDrawArrayCalled);
+            Log.v(TAG, "result = " + Arrays.toString(mResult));
+
+            // These are the four vertics that should be used.
+            int[] answer = new int[] {
+                2, 7,
+                3, 7,
+                3, 15,
+                2, 15};
+            int count[] = new int[4];
+
+            // Count the number of appearances for each vertex.
+            for (int i = 0; i < 4; i++) {
+                for (int j = 0; j < 4; j++) {
+                    if (answer[i * 2] == mResult[j * 2] &&
+                        answer[i * 2 + 1] == mResult[j * 2 + 1]) {
+                        count[i]++;
+                    }
+                }
+            }
+
+            // Each vertex should appear exactly once.
+            for (int i = 0; i < 4; i++) {
+                assertEquals(1, count[i]);
+            }
+        }
+    }
+
+    @SmallTest
+    public void testTransform() {
+        new TransformTest().run();
+    }
+
+    // This test assumes glLoadMatrixf is used to load the model view matrix,
+    // and glOrthof is used to load the projection matrix.
+    //
+    // The projection matrix is set to an orthogonal projection which is the
+    // inverse of viewport transform. So the model view matrix maps input
+    // directly to screen coordinates (default no scaling, and the y-axis is
+    // reversed).
+    //
+    // The matrix here are all listed in column major order.
+    //
+    private static class TransformTest extends GLMock {
+        private final float[] mModelViewMatrixUsed = new float[16];
+        private final float[] mProjectionMatrixUsed = new float[16];
+
+        @Override
+        public void glDrawArrays(int mode, int first, int count) {
+            copy(mModelViewMatrixUsed, mGLModelViewMatrix);
+            copy(mProjectionMatrixUsed, mGLProjectionMatrix);
+        }
+
+        private void copy(float[] dest, float[] src) {
+            System.arraycopy(src, 0, dest, 0, 16);
+        }
+
+        void run() {
+            GLCanvas canvas = new GLES11Canvas(this);
+            canvas.setSize(40, 50);
+            int color = 0;
+
+            // Initial matrix
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+            assertMatrixEq(new float[] {
+                    1,  0, 0, 0,
+                    0, -1, 0, 0,
+                    0,  0, 1, 0,
+                    0, 50, 0, 1
+                    }, mModelViewMatrixUsed);
+
+            assertMatrixEq(new float[] {
+                    2f / 40,       0,  0, 0,
+                          0, 2f / 50,  0, 0,
+                          0,       0, -1, 0,
+                         -1,      -1,  0, 1
+                    }, mProjectionMatrixUsed);
+
+            // Translation
+            canvas.translate(3, 4, 5);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+            assertMatrixEq(new float[] {
+                    1,  0, 0, 0,
+                    0, -1, 0, 0,
+                    0,  0, 1, 0,
+                    3, 46, 5, 1
+                    }, mModelViewMatrixUsed);
+            canvas.save();
+
+            // Scaling
+            canvas.scale(0.7f, 0.6f, 0.5f);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+            assertMatrixEq(new float[] {
+                    0.7f,     0,    0, 0,
+                    0,    -0.6f,    0, 0,
+                    0,        0, 0.5f, 0,
+                    3,       46,    5, 1
+                    }, mModelViewMatrixUsed);
+
+            // Rotation
+            canvas.rotate(90, 0, 0, 1);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+            assertMatrixEq(new float[] {
+                        0, -0.6f,    0, 0,
+                    -0.7f,     0,    0, 0,
+                        0,     0, 0.5f, 0,
+                        3,    46,    5, 1
+                    }, mModelViewMatrixUsed);
+            canvas.restore();
+
+            // After restoring to the point just after translation,
+            // do rotation again.
+            canvas.rotate(180, 1, 0, 0);
+            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
+            assertMatrixEq(new float[] {
+                    1,  0,  0, 0,
+                    0,  1,  0, 0,
+                    0,  0, -1, 0,
+                    3, 46,  5, 1
+                    }, mModelViewMatrixUsed);
+        }
+    }
+
+    private static void assertPremultipliedBlending(GLMock mock) {
+        assertTrue(mock.mGLBlendFuncCalled > 0);
+        assertTrue(mock.mGLBlendEnabled);
+        assertEquals(GL11.GL_ONE, mock.mGLBlendFuncSFactor);
+        assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, mock.mGLBlendFuncDFactor);
+    }
+
+    private static void assertMatrixEq(float[] expected, float[] actual) {
+        try {
+            for (int i = 0; i < 16; i++) {
+                assertFloatEq(expected[i], actual[i]);
+            }
+        } catch (Throwable t) {
+            Log.v(TAG, "expected = " + Arrays.toString(expected) +
+                    ", actual = " + Arrays.toString(actual));
+            fail();
+        }
+    }
+
+    public static void assertFloatEq(float expected, float actual) {
+        if (Math.abs(actual - expected) > 1e-6) {
+            Log.v(TAG, "expected: " + expected + ", actual: " + actual);
+            fail();
+        }
+    }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLMock.java b/tests/src/com/android/gallery3d/glrenderer/GLMock.java
new file mode 100644
index 0000000..b242217
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLMock.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import com.android.gallery3d.ui.PointerInfo;
+
+import java.nio.Buffer;
+import java.util.HashMap;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL11;
+
+public class GLMock extends GLStub {
+    @SuppressWarnings("unused")
+    private static final String TAG = "GLMock";
+
+    // glClear
+    int mGLClearCalled;
+    int mGLClearMask;
+    // glBlendFunc
+    int mGLBlendFuncCalled;
+    int mGLBlendFuncSFactor;
+    int mGLBlendFuncDFactor;
+    // glColor4[fx]
+    int mGLColorCalled;
+    int mGLColor;
+    // glEnable, glDisable
+    boolean mGLBlendEnabled;
+    boolean mGLStencilEnabled;
+    // glEnableClientState
+    boolean mGLVertexArrayEnabled;
+    // glVertexPointer
+    PointerInfo mGLVertexPointer;
+    // glMatrixMode
+    int mGLMatrixMode = GL10.GL_MODELVIEW;
+    // glLoadMatrixf
+    float[] mGLModelViewMatrix = new float[16];
+    float[] mGLProjectionMatrix = new float[16];
+    // glBindTexture
+    int mGLBindTextureId;
+    // glTexEnvf
+    HashMap<Integer, Float> mGLTexEnv0 = new HashMap<Integer, Float>();
+    HashMap<Integer, Float> mGLTexEnv1 = new HashMap<Integer, Float>();
+    // glActiveTexture
+    int mGLActiveTexture = GL11.GL_TEXTURE0;
+
+    @Override
+    public void glClear(int mask) {
+        mGLClearCalled++;
+        mGLClearMask = mask;
+    }
+
+    @Override
+    public void glBlendFunc(int sfactor, int dfactor) {
+        mGLBlendFuncSFactor = sfactor;
+        mGLBlendFuncDFactor = dfactor;
+        mGLBlendFuncCalled++;
+    }
+
+    @Override
+    public void glColor4f(float red, float green, float blue,
+        float alpha) {
+        mGLColorCalled++;
+        mGLColor = makeColor4f(red, green, blue, alpha);
+    }
+
+    @Override
+    public void glColor4x(int red, int green, int blue, int alpha) {
+        mGLColorCalled++;
+        mGLColor = makeColor4x(red, green, blue, alpha);
+    }
+
+    @Override
+    public void glEnable(int cap) {
+        if (cap == GL11.GL_BLEND) {
+            mGLBlendEnabled = true;
+        } else if (cap == GL11.GL_STENCIL_TEST) {
+            mGLStencilEnabled = true;
+        }
+    }
+
+    @Override
+    public void glDisable(int cap) {
+        if (cap == GL11.GL_BLEND) {
+            mGLBlendEnabled = false;
+        } else if (cap == GL11.GL_STENCIL_TEST) {
+            mGLStencilEnabled = false;
+        }
+    }
+
+    @Override
+    public void glEnableClientState(int array) {
+        if (array == GL10.GL_VERTEX_ARRAY) {
+           mGLVertexArrayEnabled = true;
+        }
+    }
+
+    @Override
+    public void glVertexPointer(int size, int type, int stride, Buffer pointer) {
+        mGLVertexPointer = new PointerInfo(size, type, stride, pointer);
+    }
+
+    @Override
+    public void glMatrixMode(int mode) {
+        mGLMatrixMode = mode;
+    }
+
+    @Override
+    public void glLoadMatrixf(float[] m, int offset) {
+        if (mGLMatrixMode == GL10.GL_MODELVIEW) {
+            System.arraycopy(m, offset, mGLModelViewMatrix, 0, 16);
+        } else if (mGLMatrixMode == GL10.GL_PROJECTION) {
+            System.arraycopy(m, offset, mGLProjectionMatrix, 0, 16);
+        }
+    }
+
+    @Override
+    public void glOrthof(
+        float left, float right, float bottom, float top,
+        float zNear, float zFar) {
+        float tx = -(right + left) / (right - left);
+        float ty = -(top + bottom) / (top - bottom);
+            float tz = - (zFar + zNear) / (zFar - zNear);
+            float[] m = new float[] {
+                    2 / (right - left), 0, 0,  0,
+                    0, 2 / (top - bottom), 0,  0,
+                    0, 0, -2 / (zFar - zNear), 0,
+                    tx, ty, tz, 1
+            };
+            glLoadMatrixf(m, 0);
+    }
+
+    @Override
+    public void glBindTexture(int target, int texture) {
+        if (target == GL11.GL_TEXTURE_2D) {
+            mGLBindTextureId = texture;
+        }
+    }
+
+    @Override
+    public void glTexEnvf(int target, int pname, float param) {
+        if (target == GL11.GL_TEXTURE_ENV) {
+            if (mGLActiveTexture == GL11.GL_TEXTURE0) {
+                mGLTexEnv0.put(pname, param);
+            } else if (mGLActiveTexture == GL11.GL_TEXTURE1) {
+                mGLTexEnv1.put(pname, param);
+            } else {
+                throw new AssertionError();
+            }
+        }
+    }
+
+    public int getTexEnvi(int pname) {
+        return getTexEnvi(mGLActiveTexture, pname);
+    }
+
+    public int getTexEnvi(int activeTexture, int pname) {
+        if (activeTexture == GL11.GL_TEXTURE0) {
+            return (int) mGLTexEnv0.get(pname).floatValue();
+        } else if (activeTexture == GL11.GL_TEXTURE1) {
+            return (int) mGLTexEnv1.get(pname).floatValue();
+        } else {
+            throw new AssertionError();
+        }
+    }
+
+    @Override
+    public void glActiveTexture(int texture) {
+        mGLActiveTexture = texture;
+    }
+
+    public static int makeColor4f(float red, float green, float blue,
+            float alpha) {
+        return (Math.round(alpha * 255) << 24) |
+                (Math.round(red * 255) << 16) |
+                (Math.round(green * 255) << 8) |
+                Math.round(blue * 255);
+    }
+
+    public static int makeColor4x(int red, int green, int blue, int alpha) {
+        final float X = 65536f;
+        return makeColor4f(red / X, green / X, blue / X, alpha / X);
+    }
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/GLStub.java b/tests/src/com/android/gallery3d/glrenderer/GLStub.java
new file mode 100644
index 0000000..4b66040
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/GLStub.java
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+import javax.microedition.khronos.opengles.GL10Ext;
+import javax.microedition.khronos.opengles.GL11;
+import javax.microedition.khronos.opengles.GL11Ext;
+
+public class GLStub implements GL, GL10, GL10Ext, GL11, GL11Ext {
+    @SuppressWarnings("unused")
+    private static final String TAG = "GLStub";
+
+    public void glActiveTexture(
+        int texture
+    ){}
+
+    public void glAlphaFunc(
+        int func,
+        float ref
+    ){}
+
+    public void glAlphaFuncx(
+        int func,
+        int ref
+    ){}
+
+    public void glBindTexture(
+        int target,
+        int texture
+    ){}
+
+    public void glBlendFunc(
+        int sfactor,
+        int dfactor
+    ){}
+
+    public void glClear(
+        int mask
+    ){}
+
+    public void glClearColor(
+        float red,
+        float green,
+        float blue,
+        float alpha
+    ){}
+
+    public void glClearColorx(
+        int red,
+        int green,
+        int blue,
+        int alpha
+    ){}
+
+    public void glClearDepthf(
+        float depth
+    ){}
+
+    public void glClearDepthx(
+        int depth
+    ){}
+
+    public void glClearStencil(
+        int s
+    ){}
+
+    public void glClientActiveTexture(
+        int texture
+    ){}
+
+    public void glColor4f(
+        float red,
+        float green,
+        float blue,
+        float alpha
+    ){}
+
+    public void glColor4x(
+        int red,
+        int green,
+        int blue,
+        int alpha
+    ){}
+
+    public void glColorMask(
+        boolean red,
+        boolean green,
+        boolean blue,
+        boolean alpha
+    ){}
+
+    public void glColorPointer(
+        int size,
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glCompressedTexImage2D(
+        int target,
+        int level,
+        int internalformat,
+        int width,
+        int height,
+        int border,
+        int imageSize,
+        java.nio.Buffer data
+    ){}
+
+    public void glCompressedTexSubImage2D(
+        int target,
+        int level,
+        int xoffset,
+        int yoffset,
+        int width,
+        int height,
+        int format,
+        int imageSize,
+        java.nio.Buffer data
+    ){}
+
+    public void glCopyTexImage2D(
+        int target,
+        int level,
+        int internalformat,
+        int x,
+        int y,
+        int width,
+        int height,
+        int border
+    ){}
+
+    public void glCopyTexSubImage2D(
+        int target,
+        int level,
+        int xoffset,
+        int yoffset,
+        int x,
+        int y,
+        int width,
+        int height
+    ){}
+
+    public void glCullFace(
+        int mode
+    ){}
+
+    public void glDeleteTextures(
+        int n,
+        int[] textures,
+        int offset
+    ){}
+
+    public void glDeleteTextures(
+        int n,
+        java.nio.IntBuffer textures
+    ){}
+
+    public void glDepthFunc(
+        int func
+    ){}
+
+    public void glDepthMask(
+        boolean flag
+    ){}
+
+    public void glDepthRangef(
+        float zNear,
+        float zFar
+    ){}
+
+    public void glDepthRangex(
+        int zNear,
+        int zFar
+    ){}
+
+    public void glDisable(
+        int cap
+    ){}
+
+    public void glDisableClientState(
+        int array
+    ){}
+
+    public void glDrawArrays(
+        int mode,
+        int first,
+        int count
+    ){}
+
+    public void glDrawElements(
+        int mode,
+        int count,
+        int type,
+        java.nio.Buffer indices
+    ){}
+
+    public void glEnable(
+        int cap
+    ){}
+
+    public void glEnableClientState(
+        int array
+    ){}
+
+    public void glFinish(
+    ){}
+
+    public void glFlush(
+    ){}
+
+    public void glFogf(
+        int pname,
+        float param
+    ){}
+
+    public void glFogfv(
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glFogfv(
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glFogx(
+        int pname,
+        int param
+    ){}
+
+    public void glFogxv(
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glFogxv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glFrontFace(
+        int mode
+    ){}
+
+    public void glFrustumf(
+        float left,
+        float right,
+        float bottom,
+        float top,
+        float zNear,
+        float zFar
+    ){}
+
+    public void glFrustumx(
+        int left,
+        int right,
+        int bottom,
+        int top,
+        int zNear,
+        int zFar
+    ){}
+
+    public void glGenTextures(
+        int n,
+        int[] textures,
+        int offset
+    ){}
+
+    public void glGenTextures(
+        int n,
+        java.nio.IntBuffer textures
+    ){}
+
+    public int glGetError(
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glGetIntegerv(
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetIntegerv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public String glGetString(
+        int name
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glHint(
+        int target,
+        int mode
+    ){}
+
+    public void glLightModelf(
+        int pname,
+        float param
+    ){}
+
+    public void glLightModelfv(
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glLightModelfv(
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glLightModelx(
+        int pname,
+        int param
+    ){}
+
+    public void glLightModelxv(
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glLightModelxv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glLightf(
+        int light,
+        int pname,
+        float param
+    ){}
+
+    public void glLightfv(
+        int light,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glLightfv(
+        int light,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glLightx(
+        int light,
+        int pname,
+        int param
+    ){}
+
+    public void glLightxv(
+        int light,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glLightxv(
+        int light,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glLineWidth(
+        float width
+    ){}
+
+    public void glLineWidthx(
+        int width
+    ){}
+
+    public void glLoadIdentity(
+    ){}
+
+    public void glLoadMatrixf(
+        float[] m,
+        int offset
+    ){}
+
+    public void glLoadMatrixf(
+        java.nio.FloatBuffer m
+    ){}
+
+    public void glLoadMatrixx(
+        int[] m,
+        int offset
+    ){}
+
+    public void glLoadMatrixx(
+        java.nio.IntBuffer m
+    ){}
+
+    public void glLogicOp(
+        int opcode
+    ){}
+
+    public void glMaterialf(
+        int face,
+        int pname,
+        float param
+    ){}
+
+    public void glMaterialfv(
+        int face,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glMaterialfv(
+        int face,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glMaterialx(
+        int face,
+        int pname,
+        int param
+    ){}
+
+    public void glMaterialxv(
+        int face,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glMaterialxv(
+        int face,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glMatrixMode(
+        int mode
+    ){}
+
+    public void glMultMatrixf(
+        float[] m,
+        int offset
+    ){}
+
+    public void glMultMatrixf(
+        java.nio.FloatBuffer m
+    ){}
+
+    public void glMultMatrixx(
+        int[] m,
+        int offset
+    ){}
+
+    public void glMultMatrixx(
+        java.nio.IntBuffer m
+    ){}
+
+    public void glMultiTexCoord4f(
+        int target,
+        float s,
+        float t,
+        float r,
+        float q
+    ){}
+
+    public void glMultiTexCoord4x(
+        int target,
+        int s,
+        int t,
+        int r,
+        int q
+    ){}
+
+    public void glNormal3f(
+        float nx,
+        float ny,
+        float nz
+    ){}
+
+    public void glNormal3x(
+        int nx,
+        int ny,
+        int nz
+    ){}
+
+    public void glNormalPointer(
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glOrthof(
+        float left,
+        float right,
+        float bottom,
+        float top,
+        float zNear,
+        float zFar
+    ){}
+
+    public void glOrthox(
+        int left,
+        int right,
+        int bottom,
+        int top,
+        int zNear,
+        int zFar
+    ){}
+
+    public void glPixelStorei(
+        int pname,
+        int param
+    ){}
+
+    public void glPointSize(
+        float size
+    ){}
+
+    public void glPointSizex(
+        int size
+    ){}
+
+    public void glPolygonOffset(
+        float factor,
+        float units
+    ){}
+
+    public void glPolygonOffsetx(
+        int factor,
+        int units
+    ){}
+
+    public void glPopMatrix(
+    ){}
+
+    public void glPushMatrix(
+    ){}
+
+    public void glReadPixels(
+        int x,
+        int y,
+        int width,
+        int height,
+        int format,
+        int type,
+        java.nio.Buffer pixels
+    ){}
+
+    public void glRotatef(
+        float angle,
+        float x,
+        float y,
+        float z
+    ){}
+
+    public void glRotatex(
+        int angle,
+        int x,
+        int y,
+        int z
+    ){}
+
+    public void glSampleCoverage(
+        float value,
+        boolean invert
+    ){}
+
+    public void glSampleCoveragex(
+        int value,
+        boolean invert
+    ){}
+
+    public void glScalef(
+        float x,
+        float y,
+        float z
+    ){}
+
+    public void glScalex(
+        int x,
+        int y,
+        int z
+    ){}
+
+    public void glScissor(
+        int x,
+        int y,
+        int width,
+        int height
+    ){}
+
+    public void glShadeModel(
+        int mode
+    ){}
+
+    public void glStencilFunc(
+        int func,
+        int ref,
+        int mask
+    ){}
+
+    public void glStencilMask(
+        int mask
+    ){}
+
+    public void glStencilOp(
+        int fail,
+        int zfail,
+        int zpass
+    ){}
+
+    public void glTexCoordPointer(
+        int size,
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glTexEnvf(
+        int target,
+        int pname,
+        float param
+    ){}
+
+    public void glTexEnvfv(
+        int target,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glTexEnvfv(
+        int target,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glTexEnvx(
+        int target,
+        int pname,
+        int param
+    ){}
+
+    public void glTexEnvxv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexEnvxv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glTexImage2D(
+        int target,
+        int level,
+        int internalformat,
+        int width,
+        int height,
+        int border,
+        int format,
+        int type,
+        java.nio.Buffer pixels
+    ){}
+
+    public void glTexParameterf(
+        int target,
+        int pname,
+        float param
+    ){}
+
+    public void glTexParameterx(
+        int target,
+        int pname,
+        int param
+    ){}
+
+    public void glTexSubImage2D(
+        int target,
+        int level,
+        int xoffset,
+        int yoffset,
+        int width,
+        int height,
+        int format,
+        int type,
+        java.nio.Buffer pixels
+    ){}
+
+    public void glTranslatef(
+        float x,
+        float y,
+        float z
+    ){}
+
+    public void glTranslatex(
+        int x,
+        int y,
+        int z
+    ){}
+
+    public void glVertexPointer(
+        int size,
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glViewport(
+        int x,
+        int y,
+        int width,
+        int height
+    ){}
+
+    public int glQueryMatrixxOES(
+        int[] mantissa,
+        int mantissaOffset,
+        int[] exponent,
+        int exponentOffset
+    ){ throw new UnsupportedOperationException(); }
+
+    public int glQueryMatrixxOES(
+        java.nio.IntBuffer mantissa,
+        java.nio.IntBuffer exponent
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glGetPointerv(int pname, java.nio.Buffer[] params){}
+    public void glBindBuffer(
+        int target,
+        int buffer
+    ){}
+
+    public void glBufferData(
+        int target,
+        int size,
+        java.nio.Buffer data,
+        int usage
+    ){}
+
+    public void glBufferSubData(
+        int target,
+        int offset,
+        int size,
+        java.nio.Buffer data
+    ){}
+
+    public void glClipPlanef(
+        int plane,
+        float[] equation,
+        int offset
+    ){}
+
+    public void glClipPlanef(
+        int plane,
+        java.nio.FloatBuffer equation
+    ){}
+
+    public void glClipPlanex(
+        int plane,
+        int[] equation,
+        int offset
+    ){}
+
+    public void glClipPlanex(
+        int plane,
+        java.nio.IntBuffer equation
+    ){}
+
+    public void glColor4ub(
+        byte red,
+        byte green,
+        byte blue,
+        byte alpha
+    ){}
+
+    public void glColorPointer(
+        int size,
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glDeleteBuffers(
+        int n,
+        int[] buffers,
+        int offset
+    ){}
+
+    public void glDeleteBuffers(
+        int n,
+        java.nio.IntBuffer buffers
+    ){}
+
+    public void glDrawElements(
+        int mode,
+        int count,
+        int type,
+        int offset
+    ){}
+
+    public void glGenBuffers(
+        int n,
+        int[] buffers,
+        int offset
+    ){}
+
+    public void glGenBuffers(
+        int n,
+        java.nio.IntBuffer buffers
+    ){}
+
+    public void glGetBooleanv(
+        int pname,
+        boolean[] params,
+        int offset
+    ){}
+
+    public void glGetBooleanv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetBufferParameteriv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetBufferParameteriv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetClipPlanef(
+        int pname,
+        float[] eqn,
+        int offset
+    ){}
+
+    public void glGetClipPlanef(
+        int pname,
+        java.nio.FloatBuffer eqn
+    ){}
+
+    public void glGetClipPlanex(
+        int pname,
+        int[] eqn,
+        int offset
+    ){}
+
+    public void glGetClipPlanex(
+        int pname,
+        java.nio.IntBuffer eqn
+    ){}
+
+    public void glGetFixedv(
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetFixedv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetFloatv(
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glGetFloatv(
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glGetLightfv(
+        int light,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glGetLightfv(
+        int light,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glGetLightxv(
+        int light,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetLightxv(
+        int light,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetMaterialfv(
+        int face,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glGetMaterialfv(
+        int face,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glGetMaterialxv(
+        int face,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetMaterialxv(
+        int face,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexEnviv(
+        int env,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexEnviv(
+        int env,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexEnvxv(
+        int env,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexEnvxv(
+        int env,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexParameterfv(
+        int target,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glGetTexParameterfv(
+        int target,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glGetTexParameteriv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexParameteriv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexParameterxv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexParameterxv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public boolean glIsBuffer(
+        int buffer
+    ){ throw new UnsupportedOperationException(); }
+
+    public boolean glIsEnabled(
+        int cap
+    ){ throw new UnsupportedOperationException(); }
+
+    public boolean glIsTexture(
+        int texture
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glNormalPointer(
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glPointParameterf(
+        int pname,
+        float param
+    ){}
+
+    public void glPointParameterfv(
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glPointParameterfv(
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glPointParameterx(
+        int pname,
+        int param
+    ){}
+
+    public void glPointParameterxv(
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glPointParameterxv(
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glPointSizePointerOES(
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glTexCoordPointer(
+        int size,
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glTexEnvi(
+        int target,
+        int pname,
+        int param
+    ){}
+
+    public void glTexEnviv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexEnviv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glTexParameterfv(
+        int target,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glTexParameterfv(
+        int target,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glTexParameteri(
+        int target,
+        int pname,
+        int param
+    ){}
+
+    public void glTexParameteriv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexParameteriv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glTexParameterxv(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexParameterxv(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glVertexPointer(
+        int size,
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glCurrentPaletteMatrixOES(
+        int matrixpaletteindex
+    ){}
+
+    public void glDrawTexfOES(
+        float x,
+        float y,
+        float z,
+        float width,
+        float height
+    ){}
+
+    public void glDrawTexfvOES(
+        float[] coords,
+        int offset
+    ){}
+
+    public void glDrawTexfvOES(
+        java.nio.FloatBuffer coords
+    ){}
+
+    public void glDrawTexiOES(
+        int x,
+        int y,
+        int z,
+        int width,
+        int height
+    ){}
+
+    public void glDrawTexivOES(
+        int[] coords,
+        int offset
+    ){}
+
+    public void glDrawTexivOES(
+        java.nio.IntBuffer coords
+    ){}
+
+    public void glDrawTexsOES(
+        short x,
+        short y,
+        short z,
+        short width,
+        short height
+    ){}
+
+    public void glDrawTexsvOES(
+        short[] coords,
+        int offset
+    ){}
+
+    public void glDrawTexsvOES(
+        java.nio.ShortBuffer coords
+    ){}
+
+    public void glDrawTexxOES(
+        int x,
+        int y,
+        int z,
+        int width,
+        int height
+    ){}
+
+    public void glDrawTexxvOES(
+        int[] coords,
+        int offset
+    ){}
+
+    public void glDrawTexxvOES(
+        java.nio.IntBuffer coords
+    ){}
+
+    public void glLoadPaletteFromModelViewMatrixOES(
+    ){}
+
+    public void glMatrixIndexPointerOES(
+        int size,
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glMatrixIndexPointerOES(
+        int size,
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glWeightPointerOES(
+        int size,
+        int type,
+        int stride,
+        java.nio.Buffer pointer
+    ){}
+
+    public void glWeightPointerOES(
+        int size,
+        int type,
+        int stride,
+        int offset
+    ){}
+
+    public void glBindFramebufferOES(
+        int target,
+        int framebuffer
+    ){}
+
+    public void glBindRenderbufferOES(
+        int target,
+        int renderbuffer
+    ){}
+
+    public void glBlendEquation(
+        int mode
+    ){}
+
+    public void glBlendEquationSeparate(
+        int modeRGB,
+        int modeAlpha
+    ){}
+
+    public void glBlendFuncSeparate(
+        int srcRGB,
+        int dstRGB,
+        int srcAlpha,
+        int dstAlpha
+    ){}
+
+    public int glCheckFramebufferStatusOES(
+        int target
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glDeleteFramebuffersOES(
+        int n,
+        int[] framebuffers,
+        int offset
+    ){}
+
+    public void glDeleteFramebuffersOES(
+        int n,
+        java.nio.IntBuffer framebuffers
+    ){}
+
+    public void glDeleteRenderbuffersOES(
+        int n,
+        int[] renderbuffers,
+        int offset
+    ){}
+
+    public void glDeleteRenderbuffersOES(
+        int n,
+        java.nio.IntBuffer renderbuffers
+    ){}
+
+    public void glFramebufferRenderbufferOES(
+        int target,
+        int attachment,
+        int renderbuffertarget,
+        int renderbuffer
+    ){}
+
+    public void glFramebufferTexture2DOES(
+        int target,
+        int attachment,
+        int textarget,
+        int texture,
+        int level
+    ){}
+
+    public void glGenerateMipmapOES(
+        int target
+    ){}
+
+    public void glGenFramebuffersOES(
+        int n,
+        int[] framebuffers,
+        int offset
+    ){}
+
+    public void glGenFramebuffersOES(
+        int n,
+        java.nio.IntBuffer framebuffers
+    ){}
+
+    public void glGenRenderbuffersOES(
+        int n,
+        int[] renderbuffers,
+        int offset
+    ){}
+
+    public void glGenRenderbuffersOES(
+        int n,
+        java.nio.IntBuffer renderbuffers
+    ){}
+
+    public void glGetFramebufferAttachmentParameterivOES(
+        int target,
+        int attachment,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetFramebufferAttachmentParameterivOES(
+        int target,
+        int attachment,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetRenderbufferParameterivOES(
+        int target,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetRenderbufferParameterivOES(
+        int target,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexGenfv(
+        int coord,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glGetTexGenfv(
+        int coord,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glGetTexGeniv(
+        int coord,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexGeniv(
+        int coord,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glGetTexGenxv(
+        int coord,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glGetTexGenxv(
+        int coord,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public boolean glIsFramebufferOES(
+        int framebuffer
+    ){ throw new UnsupportedOperationException(); }
+
+    public boolean glIsRenderbufferOES(
+        int renderbuffer
+    ){ throw new UnsupportedOperationException(); }
+
+    public void glRenderbufferStorageOES(
+        int target,
+        int internalformat,
+        int width,
+        int height
+    ){}
+
+    public void glTexGenf(
+        int coord,
+        int pname,
+        float param
+    ){}
+
+    public void glTexGenfv(
+        int coord,
+        int pname,
+        float[] params,
+        int offset
+    ){}
+
+    public void glTexGenfv(
+        int coord,
+        int pname,
+        java.nio.FloatBuffer params
+    ){}
+
+    public void glTexGeni(
+        int coord,
+        int pname,
+        int param
+    ){}
+
+    public void glTexGeniv(
+        int coord,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexGeniv(
+        int coord,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+
+    public void glTexGenx(
+        int coord,
+        int pname,
+        int param
+    ){}
+
+    public void glTexGenxv(
+        int coord,
+        int pname,
+        int[] params,
+        int offset
+    ){}
+
+    public void glTexGenxv(
+        int coord,
+        int pname,
+        java.nio.IntBuffer params
+    ){}
+}
diff --git a/tests/src/com/android/gallery3d/glrenderer/TextureTest.java b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java
new file mode 100644
index 0000000..956d894
--- /dev/null
+++ b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.glrenderer;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.gallery3d.glrenderer.ColorTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLES11Canvas;
+import com.android.gallery3d.glrenderer.UploadedTexture;
+
+import junit.framework.TestCase;
+
+import javax.microedition.khronos.opengles.GL11;
+
+@SmallTest
+public class TextureTest extends TestCase {
+    @SuppressWarnings("unused")
+    private static final String TAG = "TextureTest";
+
+    class MyBasicTexture extends BasicTexture {
+        int mOnBindCalled;
+        int mOpaqueCalled;
+
+        MyBasicTexture(GLCanvas canvas, int id) {
+            super(canvas, id, 0);
+        }
+
+        @Override
+        protected boolean onBind(GLCanvas canvas) {
+            mOnBindCalled++;
+            return true;
+        }
+
+        @Override
+        protected int getTarget() {
+            return GL11.GL_TEXTURE_2D;
+        }
+
+        @Override
+        public boolean isOpaque() {
+            mOpaqueCalled++;
+            return true;
+        }
+
+        void upload() {
+            mState = STATE_LOADED;
+        }
+    }
+
+    @SmallTest
+    public void testBasicTexture() {
+        GL11 glStub = new GLStub();
+        GLCanvas canvas = new GLES11Canvas(glStub);
+        MyBasicTexture texture = new MyBasicTexture(canvas, 47);
+
+        assertEquals(47, texture.getId());
+        texture.setSize(1, 1);
+        assertEquals(1, texture.getWidth());
+        assertEquals(1, texture.getHeight());
+        assertEquals(1, texture.getTextureWidth());
+        assertEquals(1, texture.getTextureHeight());
+        texture.setSize(3, 5);
+        assertEquals(3, texture.getWidth());
+        assertEquals(5, texture.getHeight());
+        assertEquals(4, texture.getTextureWidth());
+        assertEquals(8, texture.getTextureHeight());
+
+        assertFalse(texture.isLoaded());
+        texture.upload();
+        assertTrue(texture.isLoaded());
+
+        // For a different GL, it's not loaded.
+        GLCanvas canvas2 = new GLES11Canvas(glStub);
+        assertFalse(texture.isLoaded());
+
+        assertEquals(0, texture.mOnBindCalled);
+        assertEquals(0, texture.mOpaqueCalled);
+        texture.draw(canvas, 100, 200, 1, 1);
+        assertEquals(1, texture.mOnBindCalled);
+        assertEquals(1, texture.mOpaqueCalled);
+        texture.draw(canvas, 0, 0);
+        assertEquals(2, texture.mOnBindCalled);
+        assertEquals(2, texture.mOpaqueCalled);
+    }
+
+    @SmallTest
+    public void testColorTexture() {
+        GLCanvasMock canvas = new GLCanvasMock();
+        ColorTexture texture = new ColorTexture(0x12345678);
+
+        texture.setSize(42, 47);
+        assertEquals(texture.getWidth(), 42);
+        assertEquals(texture.getHeight(), 47);
+        assertEquals(0, canvas.mFillRectCalled);
+        texture.draw(canvas, 0, 0);
+        assertEquals(1, canvas.mFillRectCalled);
+        assertEquals(0x12345678, canvas.mFillRectColor);
+        assertEquals(42f, canvas.mFillRectWidth);
+        assertEquals(47f, canvas.mFillRectHeight);
+        assertFalse(texture.isOpaque());
+        assertTrue(new ColorTexture(0xFF000000).isOpaque());
+    }
+
+    private class MyUploadedTexture extends UploadedTexture {
+        int mGetCalled;
+        int mFreeCalled;
+        Bitmap mBitmap;
+        @Override
+        protected Bitmap onGetBitmap() {
+            mGetCalled++;
+            Config config = Config.ARGB_8888;
+            mBitmap = Bitmap.createBitmap(47, 42, config);
+            return mBitmap;
+        }
+        @Override
+        protected void onFreeBitmap(Bitmap bitmap) {
+            mFreeCalled++;
+            assertSame(mBitmap, bitmap);
+            mBitmap.recycle();
+            mBitmap = null;
+        }
+    }
+
+    @SmallTest
+    public void testUploadedTexture() {
+        GL11 glStub = new GLStub();
+        GLCanvas canvas = new GLES11Canvas(glStub);
+        MyUploadedTexture texture = new MyUploadedTexture();
+
+        // draw it and the bitmap should be fetched.
+        assertEquals(0, texture.mFreeCalled);
+        assertEquals(0, texture.mGetCalled);
+        texture.draw(canvas, 0, 0);
+        assertEquals(1, texture.mGetCalled);
+        assertTrue(texture.isLoaded());
+        assertTrue(texture.isContentValid());
+
+        // invalidate content and it should be freed.
+        texture.invalidateContent();
+        assertFalse(texture.isContentValid());
+        assertEquals(1, texture.mFreeCalled);
+        assertTrue(texture.isLoaded());  // But it's still loaded
+
+        // draw it again and the bitmap should be fetched again.
+        texture.draw(canvas, 0, 0);
+        assertEquals(2, texture.mGetCalled);
+        assertTrue(texture.isLoaded());
+        assertTrue(texture.isContentValid());
+
+        // recycle the texture and it should be freed again.
+        texture.recycle();
+        assertEquals(2, texture.mFreeCalled);
+        // TODO: these two are broken and waiting for fix.
+        //assertFalse(texture.isLoaded(canvas));
+        //assertFalse(texture.isContentValid(canvas));
+    }
+
+    class MyTextureForMixed extends BasicTexture {
+        MyTextureForMixed(GLCanvas canvas, int id) {
+            super(canvas, id, 0);
+        }
+
+        @Override
+        protected boolean onBind(GLCanvas canvas) {
+            return true;
+        }
+
+        @Override
+        protected int getTarget() {
+            return GL11.GL_TEXTURE_2D;
+        }
+
+        @Override
+        public boolean isOpaque() {
+            return true;
+        }
+    }
+
+    @SmallTest
+    public void testBitmapTexture() {
+        Config config = Config.ARGB_8888;
+        Bitmap bitmap = Bitmap.createBitmap(47, 42, config);
+        assertFalse(bitmap.isRecycled());
+        BitmapTexture texture = new BitmapTexture(bitmap);
+        texture.recycle();
+        assertFalse(bitmap.isRecycled());
+        bitmap.recycle();
+        assertTrue(bitmap.isRecycled());
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/CameraLatency.java b/tests/src/com/android/gallery3d/stress/CameraLatency.java
new file mode 100755
index 0000000..7177abe
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/CameraLatency.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ */
+
+public class CameraLatency extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private String TAG = "CameraLatency";
+    private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 20;
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 4000;
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+
+    private long mTotalAutoFocusTime;
+    private long mTotalShutterLag;
+    private long mTotalShutterToPictureDisplayedTime;
+    private long mTotalPictureDisplayedToJpegCallbackTime;
+    private long mTotalJpegCallbackFinishTime;
+    private long mAvgAutoFocusTime;
+    private long mAvgShutterLag = mTotalShutterLag;
+    private long mAvgShutterToPictureDisplayedTime;
+    private long mAvgPictureDisplayedToJpegCallbackTime;
+    private long mAvgJpegCallbackFinishTime;
+
+    public CameraLatency() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void testImageCapture() {
+        Log.v(TAG, "start testImageCapture test");
+        Instrumentation inst = getInstrumentation();
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        try {
+            for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                //skip the first measurement
+                if (i != 0) {
+                    CameraActivity c = getActivity();
+
+                    // if any of the latency var accessor methods return -1 then the
+                    // camera is set to a different module other than PhotoModule so
+                    // skip the shot and try again
+                    if (c.getAutoFocusTime() != -1) {
+                        mTotalAutoFocusTime += c.getAutoFocusTime();
+                        mTotalShutterLag += c.getShutterLag();
+                        mTotalShutterToPictureDisplayedTime +=
+                                c.getShutterToPictureDisplayedTime();
+                        mTotalPictureDisplayedToJpegCallbackTime +=
+                                c.getPictureDisplayedToJpegCallbackTime();
+                        mTotalJpegCallbackFinishTime += c.getJpegCallbackFinishTime();
+                    }
+                    else {
+                        i--;
+                        continue;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+        }
+        //ToDO: yslau
+        //1) Need to get the baseline from the cupcake so that we can add the
+        //failure condition of the camera latency.
+        //2) Only count those number with succesful capture. Set the timer to invalid
+        //before capture and ignore them if the value is invalid
+        int numberofRun = TOTAL_NUMBER_OF_IMAGECAPTURE - 1;
+        mAvgAutoFocusTime = mTotalAutoFocusTime / numberofRun;
+        mAvgShutterLag = mTotalShutterLag / numberofRun;
+        mAvgShutterToPictureDisplayedTime =
+                mTotalShutterToPictureDisplayedTime / numberofRun;
+        mAvgPictureDisplayedToJpegCallbackTime =
+                mTotalPictureDisplayedToJpegCallbackTime / numberofRun;
+        mAvgJpegCallbackFinishTime =
+                mTotalJpegCallbackFinishTime / numberofRun;
+
+        try {
+            FileWriter fstream = null;
+            fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            BufferedWriter out = new BufferedWriter(fstream);
+            out.write("Camera Latency : \n");
+            out.write("Number of loop: " + TOTAL_NUMBER_OF_IMAGECAPTURE + "\n");
+            out.write("Avg AutoFocus = " + mAvgAutoFocusTime + "\n");
+            out.write("Avg mShutterLag = " + mAvgShutterLag + "\n");
+            out.write("Avg mShutterToPictureDisplayedTime = "
+                    + mAvgShutterToPictureDisplayedTime + "\n");
+            out.write("Avg mPictureDisplayedToJpegCallbackTime = "
+                    + mAvgPictureDisplayedToJpegCallbackTime + "\n");
+            out.write("Avg mJpegCallbackFinishTime = " +
+                    mAvgJpegCallbackFinishTime + "\n");
+            out.close();
+            fstream.close();
+        } catch (Exception e) {
+            fail("Camera Latency write output to file");
+        }
+        Log.v(TAG, "The Image capture wait time = " +
+            WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+        Log.v(TAG, "Avg AutoFocus = " + mAvgAutoFocusTime);
+        Log.v(TAG, "Avg mShutterLag = " + mAvgShutterLag);
+        Log.v(TAG, "Avg mShutterToPictureDisplayedTime = "
+                + mAvgShutterToPictureDisplayedTime);
+        Log.v(TAG, "Avg mPictureDisplayedToJpegCallbackTime = "
+                + mAvgPictureDisplayedToJpegCallbackTime);
+        Log.v(TAG, "Avg mJpegCallbackFinishTime = " + mAvgJpegCallbackFinishTime);
+    }
+}
+
diff --git a/tests/src/com/android/gallery3d/stress/CameraStartUp.java b/tests/src/com/android/gallery3d/stress/CameraStartUp.java
new file mode 100644
index 0000000..8524465
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/CameraStartUp.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+
+/**
+ * Test cases to measure the camera and video recorder startup time.
+ */
+public class CameraStartUp extends InstrumentationTestCase {
+
+    private static final int TOTAL_NUMBER_OF_STARTUP = 20;
+
+    private String TAG = "CameraStartUp";
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private static int WAIT_TIME_FOR_PREVIEW = 1500; //1.5 second
+
+    private long launchCamera() {
+        long startupTime = 0;
+        try {
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            long beforeStart = System.currentTimeMillis();
+            Instrumentation inst = getInstrumentation();
+            Activity cameraActivity = inst.startActivitySync(intent);
+            long cameraStarted = System.currentTimeMillis();
+            Thread.sleep(WAIT_TIME_FOR_PREVIEW);
+            cameraActivity.finish();
+            startupTime = cameraStarted - beforeStart;
+            Thread.sleep(1000);
+            Log.v(TAG, "camera startup time: " + startupTime);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            fail("Fails to get the output file");
+        }
+        return startupTime;
+    }
+
+    private long launchVideo() {
+        long startupTime = 0;
+
+        try {
+            Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+            intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            long beforeStart = System.currentTimeMillis();
+            Instrumentation inst = getInstrumentation();
+            Activity recorderActivity = inst.startActivitySync(intent);
+            long cameraStarted = System.currentTimeMillis();
+            recorderActivity.finish();
+            startupTime = cameraStarted - beforeStart;
+            Log.v(TAG, "Video Startup Time = " + startupTime);
+            // wait for 1s to make sure it reach a clean stage
+            Thread.sleep(WAIT_TIME_FOR_PREVIEW);
+            Log.v(TAG, "video startup time: " + startupTime);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            fail("Fails to launch video output file");
+        }
+        return startupTime;
+    }
+
+    private void writeToOutputFile(long totalStartupTime,
+            String individualStartupTime, boolean firstStartUp, String Type) throws Exception {
+        // TODO (yslau) : Need to integrate the output data with central
+        // dashboard
+        try {
+            FileWriter fstream = null;
+            fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            BufferedWriter out = new BufferedWriter(fstream);
+            if (firstStartUp) {
+                out.write("First " + Type + " Startup: " + totalStartupTime + "\n");
+            } else {
+                long averageStartupTime = totalStartupTime / (TOTAL_NUMBER_OF_STARTUP -1);
+                out.write(Type + "startup time: " + "\n");
+                out.write("Number of loop: " + (TOTAL_NUMBER_OF_STARTUP -1)  + "\n");
+                out.write(individualStartupTime + "\n\n");
+                out.write(Type + " average startup time: " + averageStartupTime + " ms\n\n");
+            }
+            out.close();
+            fstream.close();
+        } catch (Exception e) {
+            fail("Camera write output to file");
+        }
+    }
+
+    @LargeTest
+    public void testLaunchVideo() throws Exception {
+        String individualStartupTime;
+        individualStartupTime = "Individual Video Startup Time = ";
+        long totalStartupTime = 0;
+        long startupTime = 0;
+        for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) {
+            if (i == 0) {
+                // Capture the first startup time individually
+                long firstStartUpTime = launchVideo();
+                writeToOutputFile(firstStartUpTime, "na", true, "Video");
+            } else {
+                startupTime = launchVideo();
+                totalStartupTime += startupTime;
+                individualStartupTime += startupTime + " ,";
+            }
+        }
+        Log.v(TAG, "totalStartupTime =" + totalStartupTime);
+        writeToOutputFile(totalStartupTime, individualStartupTime, false, "Video");
+    }
+
+    @LargeTest
+    public void testLaunchCamera() throws Exception {
+        String individualStartupTime;
+        individualStartupTime = "Individual Camera Startup Time = ";
+        long totalStartupTime = 0;
+        long startupTime = 0;
+        for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) {
+            if (i == 0) {
+                // Capture the first startup time individually
+                long firstStartUpTime = launchCamera();
+                writeToOutputFile(firstStartUpTime, "na", true, "Camera");
+            } else {
+                startupTime = launchCamera();
+                totalStartupTime += startupTime;
+                individualStartupTime += startupTime + " ,";
+            }
+        }
+        Log.v(TAG, "totalStartupTime =" + totalStartupTime);
+        writeToOutputFile(totalStartupTime,
+                individualStartupTime, false, "Camera");
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java
new file mode 100755
index 0000000..d3fb10d
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/CameraStressTestRunner.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.stress;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import junit.framework.TestSuite;
+
+public class CameraStressTestRunner extends InstrumentationTestRunner {
+
+    // Default recorder settings
+    public static int mVideoDuration = 20000; // set default to 20 seconds
+    public static int mVideoIterations = 1; // set default to 1 video
+    public static int mImageIterations = 10; // set default to 10 images
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(ImageCapture.class);
+        suite.addTestSuite(VideoCapture.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return CameraStressTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        String video_iterations = (String) icicle.get("video_iterations");
+        String image_iterations = (String) icicle.get("image_iterations");
+        String video_duration = (String) icicle.get("video_duration");
+
+        if ( video_iterations != null ) {
+            mVideoIterations = Integer.parseInt(video_iterations);
+        }
+        if ( image_iterations != null) {
+            mImageIterations = Integer.parseInt(image_iterations);
+        }
+        if ( video_duration != null) {
+            mVideoDuration = Integer.parseInt(video_duration);
+        }
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/ImageCapture.java b/tests/src/com/android/gallery3d/stress/ImageCapture.java
new file mode 100755
index 0000000..e322eb5
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/ImageCapture.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.stress;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.stress.CameraStressTestRunner;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.app.Activity;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.ImageCapture \
+ *    -w com.google.android.camera.tests/android.test.InstrumentationTestRunner
+ *
+ */
+
+public class ImageCapture extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private String TAG = "ImageCapture";
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1500;   //1.5 sedconds
+    private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //3 seconds
+
+    private TestUtil testUtil = new TestUtil();
+
+    // Private intent extras.
+    private final static String EXTRAS_CAMERA_FACING =
+        "android.intent.extras.CAMERA_FACING";
+
+    public ImageCapture() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        testUtil.prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        testUtil.closeOutputFile();
+        super.tearDown();
+    }
+
+    public void captureImages(String reportTag, Instrumentation inst) {
+        int total_num_of_images = CameraStressTestRunner.mImageIterations;
+        Log.v(TAG, "no of images = " + total_num_of_images);
+
+        //TODO(yslau): Need to integrate the outoput with the central dashboard,
+        //write to a txt file as a temp solution
+        boolean memoryResult = false;
+        KeyEvent focusEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS);
+
+        try {
+            testUtil.writeReportHeader(reportTag, total_num_of_images);
+            for (int i = 0; i < total_num_of_images; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeySync(focusEvent);
+                inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                testUtil.writeResult(i);
+            }
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception: " + e.toString());
+            assertTrue("testImageCapture", false);
+        }
+    }
+
+    @LargeTest
+    public void testBackImageCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent();
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureImages("Back Camera Image Capture\n", inst);
+        act.finish();
+    }
+
+    @LargeTest
+    public void testFrontImageCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent();
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureImages("Front Camera Image Capture\n", inst);
+        act.finish();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java b/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java
new file mode 100644
index 0000000..a27bd90
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/ShotToShotLatency.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 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.gallery3d.stress;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import com.android.camera.CameraActivity;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Junit / Instrumentation test case for measuring camera shot to shot latency
+ */
+public class ShotToShotLatency extends ActivityInstrumentationTestCase2<CameraActivity> {
+    private String TAG = "ShotToShotLatency";
+    private static final int TOTAL_NUMBER_OF_SNAPSHOTS = 250;
+    private static final long SNAPSHOT_WAIT = 1000;
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private static final String CAMERA_IMAGE_DIRECTORY =
+            Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/";
+
+    public ShotToShotLatency() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void cleanupLatencyImages() {
+        try {
+            File sdcard = new File(CAMERA_IMAGE_DIRECTORY);
+            File[] pics = null;
+            FilenameFilter filter = new FilenameFilter() {
+                public boolean accept(File dir, String name) {
+                    return name.endsWith(".jpg");
+                }
+            };
+            pics = sdcard.listFiles(filter);
+            for (File f : pics) {
+                f.delete();
+            }
+        } catch (SecurityException e) {
+            Log.e(TAG, "Security manager access violation: " + e.toString());
+        }
+    }
+
+    private void sleep(long time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Sleep InterruptedException " + e.toString());
+        }
+    }
+
+    @LargeTest
+    public void testShotToShotLatency() {
+        long sigmaOfDiffFromMeanSquared = 0;
+        double mean = 0;
+        double standardDeviation = 0;
+        ArrayList<Long> captureTimes = new ArrayList<Long>();
+        ArrayList<Long> latencyTimes = new ArrayList<Long>();
+
+        Log.v(TAG, "start testShotToShotLatency test");
+        Instrumentation inst = getInstrumentation();
+
+        // Generate data points
+        for (int i = 0; i < TOTAL_NUMBER_OF_SNAPSHOTS; i++) {
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+            sleep(SNAPSHOT_WAIT);
+            CameraActivity c = getActivity();
+            if (c.getCaptureStartTime() > 0) {
+                captureTimes.add(c.getCaptureStartTime());
+            }
+        }
+
+        // Calculate latencies
+        for (int j = 1; j < captureTimes.size(); j++) {
+            latencyTimes.add(captureTimes.get(j) - captureTimes.get(j - 1));
+        }
+
+        // Crunch numbers
+        for (long dataPoint : latencyTimes) {
+            mean += (double) dataPoint;
+        }
+        mean /= latencyTimes.size();
+
+        for (long dataPoint : latencyTimes) {
+            sigmaOfDiffFromMeanSquared += (dataPoint - mean) * (dataPoint - mean);
+        }
+        standardDeviation = Math.sqrt(sigmaOfDiffFromMeanSquared / latencyTimes.size());
+
+        // Report statistics
+        File outFile = new File(CAMERA_TEST_OUTPUT_FILE);
+        BufferedWriter output = null;
+        try {
+            output = new BufferedWriter(new FileWriter(outFile, true));
+            output.write("Shot to shot latency - mean: " + mean + "\n");
+            output.write("Shot to shot latency - standard deviation: " + standardDeviation + "\n");
+            cleanupLatencyImages();
+        } catch (IOException e) {
+            Log.e(TAG, "testShotToShotLatency IOException writing to log " + e.toString());
+        } finally {
+            try {
+                if (output != null) {
+                    output.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error closing file: " + e.toString());
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/SwitchPreview.java b/tests/src/com/android/gallery3d/stress/SwitchPreview.java
new file mode 100755
index 0000000..955d092
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/SwitchPreview.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.SwitchPreview \
+ *    -w com.android.camera.tests/com.android.camera.stress.CameraStressTestRunner
+ *
+ */
+public class SwitchPreview extends ActivityInstrumentationTestCase2 <CameraActivity>{
+    private String TAG = "SwitchPreview";
+    private static final int TOTAL_NUMBER_OF_SWITCHING = 200;
+    private static final long WAIT_FOR_PREVIEW = 4000;
+
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private BufferedWriter mOut;
+    private FileWriter mfstream;
+
+    public SwitchPreview() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getActivity().finish();
+        closeOutputFile();
+        super.tearDown();
+    }
+
+    private void prepareOutputFile(){
+        try{
+            mfstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            mOut = new BufferedWriter(mfstream);
+        } catch (Exception e){
+            assertTrue("Camera Switch Mode", false);
+        }
+    }
+
+    private void closeOutputFile() {
+        try {
+            mOut.write("\n");
+            mOut.close();
+            mfstream.close();
+        } catch (Exception e) {
+            assertTrue("CameraSwitchMode close output", false);
+        }
+    }
+
+    @LargeTest
+    public void testSwitchMode() {
+        //Switching the video and the video recorder mode
+        Instrumentation inst = getInstrumentation();
+        try{
+            mOut.write("Camera Switch Mode:\n");
+            mOut.write("No of loops :" + TOTAL_NUMBER_OF_SWITCHING + "\n");
+            mOut.write("loop: ");
+            for (int i=0; i< TOTAL_NUMBER_OF_SWITCHING; i++) {
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                intent.setClass(getInstrumentation().getTargetContext(),
+                        CameraActivity.class);
+                getActivity().startActivity(intent);
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                intent = new Intent();
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                intent.setClass(getInstrumentation().getTargetContext(),
+                        CameraActivity.class);
+                getActivity().startActivity(intent);
+                mOut.write(" ," + i);
+                mOut.flush();
+            }
+        } catch (Exception e){
+            Log.v(TAG, "Got exception", e);
+        }
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/TestUtil.java b/tests/src/com/android/gallery3d/stress/TestUtil.java
new file mode 100644
index 0000000..56ab715
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/TestUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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.gallery3d.stress;
+
+import android.os.Environment;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+
+
+/**
+ * Collection of utility functions used for the test.
+ */
+public class TestUtil {
+    public BufferedWriter mOut;
+    public FileWriter mfstream;
+
+    public TestUtil() {
+    }
+
+    public void prepareOutputFile() throws Exception {
+        String camera_test_output_file =
+                Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+        mfstream = new FileWriter(camera_test_output_file, true);
+        mOut = new BufferedWriter(mfstream);
+    }
+
+    public void closeOutputFile() throws Exception {
+        mOut.write("\n");
+        mOut.close();
+        mfstream.close();
+    }
+
+    public void writeReportHeader(String reportTag, int iteration) throws Exception {
+        mOut.write(reportTag);
+        mOut.write("No of loops :" + iteration + "\n");
+        mOut.write("loop: ");
+    }
+
+    public void writeResult(int iteration) throws Exception {
+        mOut.write(" ," + iteration);
+        mOut.flush();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/stress/VideoCapture.java b/tests/src/com/android/gallery3d/stress/VideoCapture.java
new file mode 100755
index 0000000..dbbd124
--- /dev/null
+++ b/tests/src/com/android/gallery3d/stress/VideoCapture.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.stress;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.stress.TestUtil;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+
+import com.android.gallery3d.stress.CameraStressTestRunner;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.VideoCapture \
+ *    -w com.google.android.camera.tests/android.test.InstrumentationTestRunner
+ *
+ */
+
+public class VideoCapture extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private static final long WAIT_FOR_PREVIEW = 1500; //1.5 seconds
+    private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //2 seconds
+
+    // Private intent extras which control the camera facing.
+    private final static String EXTRAS_CAMERA_FACING =
+        "android.intent.extras.CAMERA_FACING";
+
+    private TestUtil testUtil = new TestUtil();
+
+    public VideoCapture() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        testUtil.prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        testUtil.closeOutputFile();
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void captureVideos(String reportTag, Instrumentation inst) throws Exception{
+        boolean memoryResult = false;
+        int total_num_of_videos = CameraStressTestRunner.mVideoIterations;
+        int video_duration = CameraStressTestRunner.mVideoDuration;
+        testUtil.writeReportHeader(reportTag, total_num_of_videos);
+
+        for (int i = 0; i < total_num_of_videos; i++) {
+            Thread.sleep(WAIT_FOR_PREVIEW);
+            // record a video
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+            Thread.sleep(video_duration);
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+            testUtil.writeResult(i);
+        }
+    }
+
+    @LargeTest
+    public void testBackVideoCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureVideos("Back Camera Video Capture\n", inst);
+        act.finish();
+    }
+
+    @LargeTest
+    public void testFrontVideoCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureVideos("Front Camera Video Capture\n", inst);
+        act.finish();
+    }
+}
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasMock.java b/tests/src/com/android/gallery3d/ui/GLCanvasMock.java
deleted file mode 100644
index f8100dd..0000000
--- a/tests/src/com/android/gallery3d/ui/GLCanvasMock.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL11;
-
-public class GLCanvasMock extends GLCanvasStub {
-    // fillRect
-    int mFillRectCalled;
-    float mFillRectWidth;
-    float mFillRectHeight;
-    int mFillRectColor;
-    // drawMixed
-    int mDrawMixedCalled;
-    float mDrawMixedRatio;
-    // drawTexture;
-    int mDrawTextureCalled;
-
-    private GL11 mGL;
-
-    public GLCanvasMock(GL11 gl) {
-        mGL = gl;
-    }
-
-    public GLCanvasMock() {
-        mGL = new GLStub();
-    }
-
-    @Override
-    public GL11 getGLInstance() {
-        return mGL;
-    }
-
-    @Override
-    public void fillRect(float x, float y, float width, float height, int color) {
-        mFillRectCalled++;
-        mFillRectWidth = width;
-        mFillRectHeight = height;
-        mFillRectColor = color;
-    }
-
-    @Override
-    public void drawTexture(
-                BasicTexture texture, int x, int y, int width, int height) {
-        mDrawTextureCalled++;
-    }
-
-    @Override
-    public void drawMixed(BasicTexture from, BasicTexture to,
-            float ratio, int x, int y, int w, int h) {
-        mDrawMixedCalled++;
-        mDrawMixedRatio = ratio;
-    }
-}
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
index 5a08b85..01f0350 100644
--- a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
+++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java
@@ -16,52 +16,84 @@
 
 package com.android.gallery3d.ui;
 
+import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.graphics.RectF;
 
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.GLCanvas;
+import com.android.gallery3d.glrenderer.GLId;
+import com.android.gallery3d.glrenderer.GLPaint;
+import com.android.gallery3d.glrenderer.RawTexture;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
 import javax.microedition.khronos.opengles.GL11;
 
 public class GLCanvasStub implements GLCanvas {
+    @Override
     public void setSize(int width, int height) {}
+    @Override
     public void clearBuffer() {}
+    @Override
     public void clearBuffer(float[] argb) {}
     public void setCurrentAnimationTimeMillis(long time) {}
     public long currentAnimationTimeMillis() {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void setAlpha(float alpha) {}
+    @Override
     public float getAlpha() {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void multiplyAlpha(float alpha) {}
+    @Override
     public void translate(float x, float y, float z) {}
+    @Override
     public void translate(float x, float y) {}
+    @Override
     public void scale(float sx, float sy, float sz) {}
+    @Override
     public void rotate(float angle, float x, float y, float z) {}
     public boolean clipRect(int left, int top, int right, int bottom) {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void save() {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void save(int saveFlags) {
         throw new UnsupportedOperationException();
     }
     public void setBlendEnabled(boolean enabled) {}
+    @Override
     public void restore() {}
+    @Override
     public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {}
+    @Override
     public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint) {}
+    @Override
     public void fillRect(float x, float y, float width, float height, int color) {}
+    @Override
     public void drawTexture(
             BasicTexture texture, int x, int y, int width, int height) {}
+    @Override
     public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer,
             int uvBuffer, int indexBuffer, int indexCount) {}
     public void drawTexture(BasicTexture texture,
             int x, int y, int width, int height, float alpha) {}
+    @Override
     public void drawTexture(BasicTexture texture, RectF source, RectF target) {}
+    @Override
     public void drawTexture(BasicTexture texture, float[] mTextureTransform,
             int x, int y, int w, int h) {}
     public void drawMixed(BasicTexture from, BasicTexture to,
             float ratio, int x, int y, int w, int h) {}
+    @Override
     public void drawMixed(BasicTexture from, int to,
             float ratio, int x, int y, int w, int h) {}
     public void drawMixed(BasicTexture from, BasicTexture to,
@@ -72,17 +104,57 @@
     public GL11 getGLInstance() {
         throw new UnsupportedOperationException();
     }
+    @Override
     public boolean unloadTexture(BasicTexture texture) {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void deleteBuffer(int bufferId) {
         throw new UnsupportedOperationException();
     }
+    @Override
     public void deleteRecycledResources() {}
+    @Override
     public void multiplyMatrix(float[] mMatrix, int offset) {}
+    @Override
     public void dumpStatisticsAndClear() {}
+    @Override
     public void beginRenderTarget(RawTexture texture) {}
+    @Override
     public void endRenderTarget() {}
+    @Override
     public void drawMixed(BasicTexture from, int toColor,
             float ratio, RectF src, RectF target) {}
+
+    @Override
+    public void setTextureParameters(BasicTexture texture) {
+    }
+    @Override
+    public void initializeTextureSize(BasicTexture texture, int format, int type) {
+    }
+    @Override
+    public void initializeTexture(BasicTexture texture, Bitmap bitmap) {
+    }
+    @Override
+    public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap,
+            int format, int type) {
+    }
+    @Override
+    public int uploadBuffer(ByteBuffer buffer) {
+        return 0;
+    }
+    @Override
+    public int uploadBuffer(FloatBuffer buffer) {
+        return 0;
+    }
+    @Override
+    public void recoverFromLightCycle() {
+    }
+    @Override
+    public void getBounds(Rect bounds, int x, int y, int width, int height) {
+    }
+    @Override
+    public GLId getGLId() {
+        return null;
+    }
 }
diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java b/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
deleted file mode 100644
index 72ccbfb..0000000
--- a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
+++ /dev/null
@@ -1,394 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL11;
-
-@SmallTest
-public class GLCanvasTest extends TestCase {
-    private static final String TAG = "GLCanvasTest";
-
-    private static GLPaint newColorPaint(int color) {
-        GLPaint paint = new GLPaint();
-        paint.setColor(color);
-        return paint;
-    }
-
-    @SmallTest
-    public void testSetSize() {
-        GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
-        canvas.setSize(100, 200);
-        canvas.setSize(1000, 100);
-        try {
-            canvas.setSize(-1, 100);
-            fail();
-        } catch (Throwable ex) {
-            // expected.
-        }
-    }
-
-    @SmallTest
-    public void testClearBuffer() {
-        new ClearBufferTest().run();
-    }
-
-    private static class ClearBufferTest extends GLMock {
-        void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
-            assertEquals(0, mGLClearCalled);
-            canvas.clearBuffer();
-            assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask);
-            assertEquals(1, mGLClearCalled);
-        }
-    }
-
-    @SmallTest
-    public void testSetColor() {
-        new SetColorTest().run();
-    }
-
-    // This test assumes we use pre-multipled alpha blending and should
-    // set the blending function and color correctly.
-    private static class SetColorTest extends GLMock {
-        void run() {
-            int[] testColors = new int[] {
-                0, 0xFFFFFFFF, 0xFF000000, 0x00FFFFFF, 0x80FF8001,
-                0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D
-            };
-
-            GLCanvas canvas = new GLCanvasImpl(this);
-            canvas.setSize(400, 300);
-            // Test one color to make sure blend function is set.
-            assertEquals(0, mGLColorCalled);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(0x7F804020));
-            assertEquals(1, mGLColorCalled);
-            assertEquals(0x7F402010, mGLColor);
-            assertPremultipliedBlending(this);
-
-            // Test other colors to make sure premultiplication is right
-            for (int c : testColors) {
-                float a = (c >>> 24) / 255f;
-                float r = ((c >> 16) & 0xff) / 255f;
-                float g = ((c >> 8) & 0xff) / 255f;
-                float b = (c & 0xff) / 255f;
-                int pre = makeColor4f(a * r, a * g, a * b, a);
-
-                mGLColorCalled = 0;
-                canvas.drawLine(0, 0, 1, 1, newColorPaint(c));
-                assertEquals(1, mGLColorCalled);
-                assertEquals(pre, mGLColor);
-            }
-        }
-    }
-
-    @SmallTest
-    public void testSetGetMultiplyAlpha() {
-        GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
-
-        canvas.setAlpha(1f);
-        assertEquals(1f, canvas.getAlpha());
-
-        canvas.setAlpha(0f);
-        assertEquals(0f, canvas.getAlpha());
-
-        canvas.setAlpha(0.5f);
-        assertEquals(0.5f, canvas.getAlpha());
-
-        canvas.multiplyAlpha(0.5f);
-        assertEquals(0.25f, canvas.getAlpha());
-
-        canvas.multiplyAlpha(0f);
-        assertEquals(0f, canvas.getAlpha());
-
-        try {
-            canvas.setAlpha(-0.01f);
-            fail();
-        } catch (Throwable ex) {
-            // expected.
-        }
-
-        try {
-            canvas.setAlpha(1.01f);
-            fail();
-        } catch (Throwable ex) {
-            // expected.
-        }
-    }
-
-    @SmallTest
-    public void testAlpha() {
-        new AlphaTest().run();
-    }
-
-    private static class AlphaTest extends GLMock {
-        void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
-            canvas.setSize(400, 300);
-
-            assertEquals(0, mGLColorCalled);
-            canvas.setAlpha(0.48f);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(0xFF804020));
-            assertPremultipliedBlending(this);
-            assertEquals(1, mGLColorCalled);
-            assertEquals(0x7A3D1F0F, mGLColor);
-        }
-    }
-
-    @SmallTest
-    public void testDrawLine() {
-        new DrawLineTest().run();
-    }
-
-    // This test assumes the drawLine() function use glDrawArrays() with
-    // GL_LINE_STRIP mode to draw the line and the input coordinates are used
-    // directly.
-    private static class DrawLineTest extends GLMock {
-        private int mDrawArrayCalled = 0;
-        private final int[] mResult = new int[4];
-
-        @Override
-        public void glDrawArrays(int mode, int first, int count) {
-            assertNotNull(mGLVertexPointer);
-            assertEquals(GL10.GL_LINE_STRIP, mode);
-            assertEquals(2, count);
-            mGLVertexPointer.bindByteBuffer();
-
-            double[] coord = new double[4];
-            mGLVertexPointer.getArrayElement(first, coord);
-            mResult[0] = (int) coord[0];
-            mResult[1] = (int) coord[1];
-            mGLVertexPointer.getArrayElement(first + 1, coord);
-            mResult[2] = (int) coord[0];
-            mResult[3] = (int) coord[1];
-            mDrawArrayCalled++;
-        }
-
-        void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
-            canvas.setSize(400, 300);
-            canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */);
-            assertTrue(mGLVertexArrayEnabled);
-            assertEquals(1, mDrawArrayCalled);
-
-            Log.v(TAG, "result = " + Arrays.toString(mResult));
-            int[] answer = new int[] {2, 7, 1, 8};
-            for (int i = 0; i < answer.length; i++) {
-                assertEquals(answer[i], mResult[i]);
-            }
-        }
-    }
-
-    @SmallTest
-    public void testFillRect() {
-        new FillRectTest().run();
-    }
-
-    // This test assumes the drawLine() function use glDrawArrays() with
-    // GL_TRIANGLE_STRIP mode to draw the line and the input coordinates
-    // are used directly.
-    private static class FillRectTest extends GLMock {
-        private int mDrawArrayCalled = 0;
-        private final int[] mResult = new int[8];
-
-        @Override
-        public void glDrawArrays(int mode, int first, int count) {
-            assertNotNull(mGLVertexPointer);
-            assertEquals(GL10.GL_TRIANGLE_STRIP, mode);
-            assertEquals(4, count);
-            mGLVertexPointer.bindByteBuffer();
-
-            double[] coord = new double[4];
-            for (int i = 0; i < 4; i++) {
-                mGLVertexPointer.getArrayElement(first + i, coord);
-                mResult[i * 2 + 0] = (int) coord[0];
-                mResult[i * 2 + 1] = (int) coord[1];
-            }
-
-            mDrawArrayCalled++;
-        }
-
-        void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
-            canvas.setSize(400, 300);
-            canvas.fillRect(2, 7, 1, 8, 0 /* color */);
-            assertTrue(mGLVertexArrayEnabled);
-            assertEquals(1, mDrawArrayCalled);
-            Log.v(TAG, "result = " + Arrays.toString(mResult));
-
-            // These are the four vertics that should be used.
-            int[] answer = new int[] {
-                2, 7,
-                3, 7,
-                3, 15,
-                2, 15};
-            int count[] = new int[4];
-
-            // Count the number of appearances for each vertex.
-            for (int i = 0; i < 4; i++) {
-                for (int j = 0; j < 4; j++) {
-                    if (answer[i * 2] == mResult[j * 2] &&
-                        answer[i * 2 + 1] == mResult[j * 2 + 1]) {
-                        count[i]++;
-                    }
-                }
-            }
-
-            // Each vertex should appear exactly once.
-            for (int i = 0; i < 4; i++) {
-                assertEquals(1, count[i]);
-            }
-        }
-    }
-
-    @SmallTest
-    public void testTransform() {
-        new TransformTest().run();
-    }
-
-    // This test assumes glLoadMatrixf is used to load the model view matrix,
-    // and glOrthof is used to load the projection matrix.
-    //
-    // The projection matrix is set to an orthogonal projection which is the
-    // inverse of viewport transform. So the model view matrix maps input
-    // directly to screen coordinates (default no scaling, and the y-axis is
-    // reversed).
-    //
-    // The matrix here are all listed in column major order.
-    //
-    private static class TransformTest extends GLMock {
-        private final float[] mModelViewMatrixUsed = new float[16];
-        private final float[] mProjectionMatrixUsed = new float[16];
-
-        @Override
-        public void glDrawArrays(int mode, int first, int count) {
-            copy(mModelViewMatrixUsed, mGLModelViewMatrix);
-            copy(mProjectionMatrixUsed, mGLProjectionMatrix);
-        }
-
-        private void copy(float[] dest, float[] src) {
-            System.arraycopy(src, 0, dest, 0, 16);
-        }
-
-        void run() {
-            GLCanvas canvas = new GLCanvasImpl(this);
-            canvas.setSize(40, 50);
-            int color = 0;
-
-            // Initial matrix
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
-            assertMatrixEq(new float[] {
-                    1,  0, 0, 0,
-                    0, -1, 0, 0,
-                    0,  0, 1, 0,
-                    0, 50, 0, 1
-                    }, mModelViewMatrixUsed);
-
-            assertMatrixEq(new float[] {
-                    2f / 40,       0,  0, 0,
-                          0, 2f / 50,  0, 0,
-                          0,       0, -1, 0,
-                         -1,      -1,  0, 1
-                    }, mProjectionMatrixUsed);
-
-            // Translation
-            canvas.translate(3, 4, 5);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
-            assertMatrixEq(new float[] {
-                    1,  0, 0, 0,
-                    0, -1, 0, 0,
-                    0,  0, 1, 0,
-                    3, 46, 5, 1
-                    }, mModelViewMatrixUsed);
-            canvas.save();
-
-            // Scaling
-            canvas.scale(0.7f, 0.6f, 0.5f);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
-            assertMatrixEq(new float[] {
-                    0.7f,     0,    0, 0,
-                    0,    -0.6f,    0, 0,
-                    0,        0, 0.5f, 0,
-                    3,       46,    5, 1
-                    }, mModelViewMatrixUsed);
-
-            // Rotation
-            canvas.rotate(90, 0, 0, 1);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
-            assertMatrixEq(new float[] {
-                        0, -0.6f,    0, 0,
-                    -0.7f,     0,    0, 0,
-                        0,     0, 0.5f, 0,
-                        3,    46,    5, 1
-                    }, mModelViewMatrixUsed);
-            canvas.restore();
-
-            // After restoring to the point just after translation,
-            // do rotation again.
-            canvas.rotate(180, 1, 0, 0);
-            canvas.drawLine(0, 0, 1, 1, newColorPaint(color));
-            assertMatrixEq(new float[] {
-                    1,  0,  0, 0,
-                    0,  1,  0, 0,
-                    0,  0, -1, 0,
-                    3, 46,  5, 1
-                    }, mModelViewMatrixUsed);
-        }
-    }
-
-    @SmallTest
-    public void testGetGLInstance() {
-        GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
-        assertSame(glStub, canvas.getGLInstance());
-    }
-
-    private static void assertPremultipliedBlending(GLMock mock) {
-        assertTrue(mock.mGLBlendFuncCalled > 0);
-        assertTrue(mock.mGLBlendEnabled);
-        assertEquals(GL11.GL_ONE, mock.mGLBlendFuncSFactor);
-        assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, mock.mGLBlendFuncDFactor);
-    }
-
-    private static void assertMatrixEq(float[] expected, float[] actual) {
-        try {
-            for (int i = 0; i < 16; i++) {
-                assertFloatEq(expected[i], actual[i]);
-            }
-        } catch (Throwable t) {
-            Log.v(TAG, "expected = " + Arrays.toString(expected) +
-                    ", actual = " + Arrays.toString(actual));
-            fail();
-        }
-    }
-
-    public static void assertFloatEq(float expected, float actual) {
-        if (Math.abs(actual - expected) > 1e-6) {
-            Log.v(TAG, "expected: " + expected + ", actual: " + actual);
-            fail();
-        }
-    }
-}
diff --git a/tests/src/com/android/gallery3d/ui/GLMock.java b/tests/src/com/android/gallery3d/ui/GLMock.java
deleted file mode 100644
index c1fe53c..0000000
--- a/tests/src/com/android/gallery3d/ui/GLMock.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import java.nio.Buffer;
-import java.util.HashMap;
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL11;
-
-public class GLMock extends GLStub {
-    @SuppressWarnings("unused")
-    private static final String TAG = "GLMock";
-
-    // glClear
-    int mGLClearCalled;
-    int mGLClearMask;
-    // glBlendFunc
-    int mGLBlendFuncCalled;
-    int mGLBlendFuncSFactor;
-    int mGLBlendFuncDFactor;
-    // glColor4[fx]
-    int mGLColorCalled;
-    int mGLColor;
-    // glEnable, glDisable
-    boolean mGLBlendEnabled;
-    boolean mGLStencilEnabled;
-    // glEnableClientState
-    boolean mGLVertexArrayEnabled;
-    // glVertexPointer
-    PointerInfo mGLVertexPointer;
-    // glMatrixMode
-    int mGLMatrixMode = GL10.GL_MODELVIEW;
-    // glLoadMatrixf
-    float[] mGLModelViewMatrix = new float[16];
-    float[] mGLProjectionMatrix = new float[16];
-    // glBindTexture
-    int mGLBindTextureId;
-    // glTexEnvf
-    HashMap<Integer, Float> mGLTexEnv0 = new HashMap<Integer, Float>();
-    HashMap<Integer, Float> mGLTexEnv1 = new HashMap<Integer, Float>();
-    // glActiveTexture
-    int mGLActiveTexture = GL11.GL_TEXTURE0;
-
-    @Override
-    public void glClear(int mask) {
-        mGLClearCalled++;
-        mGLClearMask = mask;
-    }
-
-    @Override
-    public void glBlendFunc(int sfactor, int dfactor) {
-        mGLBlendFuncSFactor = sfactor;
-        mGLBlendFuncDFactor = dfactor;
-        mGLBlendFuncCalled++;
-    }
-
-    @Override
-    public void glColor4f(float red, float green, float blue,
-        float alpha) {
-        mGLColorCalled++;
-        mGLColor = makeColor4f(red, green, blue, alpha);
-    }
-
-    @Override
-    public void glColor4x(int red, int green, int blue, int alpha) {
-        mGLColorCalled++;
-        mGLColor = makeColor4x(red, green, blue, alpha);
-    }
-
-    @Override
-    public void glEnable(int cap) {
-        if (cap == GL11.GL_BLEND) {
-            mGLBlendEnabled = true;
-        } else if (cap == GL11.GL_STENCIL_TEST) {
-            mGLStencilEnabled = true;
-        }
-    }
-
-    @Override
-    public void glDisable(int cap) {
-        if (cap == GL11.GL_BLEND) {
-            mGLBlendEnabled = false;
-        } else if (cap == GL11.GL_STENCIL_TEST) {
-            mGLStencilEnabled = false;
-        }
-    }
-
-    @Override
-    public void glEnableClientState(int array) {
-        if (array == GL10.GL_VERTEX_ARRAY) {
-           mGLVertexArrayEnabled = true;
-        }
-    }
-
-    @Override
-    public void glVertexPointer(int size, int type, int stride, Buffer pointer) {
-        mGLVertexPointer = new PointerInfo(size, type, stride, pointer);
-    }
-
-    @Override
-    public void glMatrixMode(int mode) {
-        mGLMatrixMode = mode;
-    }
-
-    @Override
-    public void glLoadMatrixf(float[] m, int offset) {
-        if (mGLMatrixMode == GL10.GL_MODELVIEW) {
-            System.arraycopy(m, offset, mGLModelViewMatrix, 0, 16);
-        } else if (mGLMatrixMode == GL10.GL_PROJECTION) {
-            System.arraycopy(m, offset, mGLProjectionMatrix, 0, 16);
-        }
-    }
-
-    @Override
-    public void glOrthof(
-        float left, float right, float bottom, float top,
-        float zNear, float zFar) {
-        float tx = -(right + left) / (right - left);
-        float ty = -(top + bottom) / (top - bottom);
-            float tz = - (zFar + zNear) / (zFar - zNear);
-            float[] m = new float[] {
-                    2 / (right - left), 0, 0,  0,
-                    0, 2 / (top - bottom), 0,  0,
-                    0, 0, -2 / (zFar - zNear), 0,
-                    tx, ty, tz, 1
-            };
-            glLoadMatrixf(m, 0);
-    }
-
-    @Override
-    public void glBindTexture(int target, int texture) {
-        if (target == GL11.GL_TEXTURE_2D) {
-            mGLBindTextureId = texture;
-        }
-    }
-
-    @Override
-    public void glTexEnvf(int target, int pname, float param) {
-        if (target == GL11.GL_TEXTURE_ENV) {
-            if (mGLActiveTexture == GL11.GL_TEXTURE0) {
-                mGLTexEnv0.put(pname, param);
-            } else if (mGLActiveTexture == GL11.GL_TEXTURE1) {
-                mGLTexEnv1.put(pname, param);
-            } else {
-                throw new AssertionError();
-            }
-        }
-    }
-
-    public int getTexEnvi(int pname) {
-        return getTexEnvi(mGLActiveTexture, pname);
-    }
-
-    public int getTexEnvi(int activeTexture, int pname) {
-        if (activeTexture == GL11.GL_TEXTURE0) {
-            return (int) mGLTexEnv0.get(pname).floatValue();
-        } else if (activeTexture == GL11.GL_TEXTURE1) {
-            return (int) mGLTexEnv1.get(pname).floatValue();
-        } else {
-            throw new AssertionError();
-        }
-    }
-
-    @Override
-    public void glActiveTexture(int texture) {
-        mGLActiveTexture = texture;
-    }
-
-    public static int makeColor4f(float red, float green, float blue,
-            float alpha) {
-        return (Math.round(alpha * 255) << 24) |
-                (Math.round(red * 255) << 16) |
-                (Math.round(green * 255) << 8) |
-                Math.round(blue * 255);
-    }
-
-    public static int makeColor4x(int red, int green, int blue, int alpha) {
-        final float X = 65536f;
-        return makeColor4f(red / X, green / X, blue / X, alpha / X);
-    }
-}
diff --git a/tests/src/com/android/gallery3d/ui/GLStub.java b/tests/src/com/android/gallery3d/ui/GLStub.java
deleted file mode 100644
index 2af73f9..0000000
--- a/tests/src/com/android/gallery3d/ui/GLStub.java
+++ /dev/null
@@ -1,1490 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import javax.microedition.khronos.opengles.GL;
-import javax.microedition.khronos.opengles.GL10;
-import javax.microedition.khronos.opengles.GL10Ext;
-import javax.microedition.khronos.opengles.GL11;
-import javax.microedition.khronos.opengles.GL11Ext;
-
-public class GLStub implements GL, GL10, GL10Ext, GL11, GL11Ext {
-    @SuppressWarnings("unused")
-    private static final String TAG = "GLStub";
-
-    public void glActiveTexture(
-        int texture
-    ){}
-
-    public void glAlphaFunc(
-        int func,
-        float ref
-    ){}
-
-    public void glAlphaFuncx(
-        int func,
-        int ref
-    ){}
-
-    public void glBindTexture(
-        int target,
-        int texture
-    ){}
-
-    public void glBlendFunc(
-        int sfactor,
-        int dfactor
-    ){}
-
-    public void glClear(
-        int mask
-    ){}
-
-    public void glClearColor(
-        float red,
-        float green,
-        float blue,
-        float alpha
-    ){}
-
-    public void glClearColorx(
-        int red,
-        int green,
-        int blue,
-        int alpha
-    ){}
-
-    public void glClearDepthf(
-        float depth
-    ){}
-
-    public void glClearDepthx(
-        int depth
-    ){}
-
-    public void glClearStencil(
-        int s
-    ){}
-
-    public void glClientActiveTexture(
-        int texture
-    ){}
-
-    public void glColor4f(
-        float red,
-        float green,
-        float blue,
-        float alpha
-    ){}
-
-    public void glColor4x(
-        int red,
-        int green,
-        int blue,
-        int alpha
-    ){}
-
-    public void glColorMask(
-        boolean red,
-        boolean green,
-        boolean blue,
-        boolean alpha
-    ){}
-
-    public void glColorPointer(
-        int size,
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glCompressedTexImage2D(
-        int target,
-        int level,
-        int internalformat,
-        int width,
-        int height,
-        int border,
-        int imageSize,
-        java.nio.Buffer data
-    ){}
-
-    public void glCompressedTexSubImage2D(
-        int target,
-        int level,
-        int xoffset,
-        int yoffset,
-        int width,
-        int height,
-        int format,
-        int imageSize,
-        java.nio.Buffer data
-    ){}
-
-    public void glCopyTexImage2D(
-        int target,
-        int level,
-        int internalformat,
-        int x,
-        int y,
-        int width,
-        int height,
-        int border
-    ){}
-
-    public void glCopyTexSubImage2D(
-        int target,
-        int level,
-        int xoffset,
-        int yoffset,
-        int x,
-        int y,
-        int width,
-        int height
-    ){}
-
-    public void glCullFace(
-        int mode
-    ){}
-
-    public void glDeleteTextures(
-        int n,
-        int[] textures,
-        int offset
-    ){}
-
-    public void glDeleteTextures(
-        int n,
-        java.nio.IntBuffer textures
-    ){}
-
-    public void glDepthFunc(
-        int func
-    ){}
-
-    public void glDepthMask(
-        boolean flag
-    ){}
-
-    public void glDepthRangef(
-        float zNear,
-        float zFar
-    ){}
-
-    public void glDepthRangex(
-        int zNear,
-        int zFar
-    ){}
-
-    public void glDisable(
-        int cap
-    ){}
-
-    public void glDisableClientState(
-        int array
-    ){}
-
-    public void glDrawArrays(
-        int mode,
-        int first,
-        int count
-    ){}
-
-    public void glDrawElements(
-        int mode,
-        int count,
-        int type,
-        java.nio.Buffer indices
-    ){}
-
-    public void glEnable(
-        int cap
-    ){}
-
-    public void glEnableClientState(
-        int array
-    ){}
-
-    public void glFinish(
-    ){}
-
-    public void glFlush(
-    ){}
-
-    public void glFogf(
-        int pname,
-        float param
-    ){}
-
-    public void glFogfv(
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glFogfv(
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glFogx(
-        int pname,
-        int param
-    ){}
-
-    public void glFogxv(
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glFogxv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glFrontFace(
-        int mode
-    ){}
-
-    public void glFrustumf(
-        float left,
-        float right,
-        float bottom,
-        float top,
-        float zNear,
-        float zFar
-    ){}
-
-    public void glFrustumx(
-        int left,
-        int right,
-        int bottom,
-        int top,
-        int zNear,
-        int zFar
-    ){}
-
-    public void glGenTextures(
-        int n,
-        int[] textures,
-        int offset
-    ){}
-
-    public void glGenTextures(
-        int n,
-        java.nio.IntBuffer textures
-    ){}
-
-    public int glGetError(
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glGetIntegerv(
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetIntegerv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public String glGetString(
-        int name
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glHint(
-        int target,
-        int mode
-    ){}
-
-    public void glLightModelf(
-        int pname,
-        float param
-    ){}
-
-    public void glLightModelfv(
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glLightModelfv(
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glLightModelx(
-        int pname,
-        int param
-    ){}
-
-    public void glLightModelxv(
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glLightModelxv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glLightf(
-        int light,
-        int pname,
-        float param
-    ){}
-
-    public void glLightfv(
-        int light,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glLightfv(
-        int light,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glLightx(
-        int light,
-        int pname,
-        int param
-    ){}
-
-    public void glLightxv(
-        int light,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glLightxv(
-        int light,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glLineWidth(
-        float width
-    ){}
-
-    public void glLineWidthx(
-        int width
-    ){}
-
-    public void glLoadIdentity(
-    ){}
-
-    public void glLoadMatrixf(
-        float[] m,
-        int offset
-    ){}
-
-    public void glLoadMatrixf(
-        java.nio.FloatBuffer m
-    ){}
-
-    public void glLoadMatrixx(
-        int[] m,
-        int offset
-    ){}
-
-    public void glLoadMatrixx(
-        java.nio.IntBuffer m
-    ){}
-
-    public void glLogicOp(
-        int opcode
-    ){}
-
-    public void glMaterialf(
-        int face,
-        int pname,
-        float param
-    ){}
-
-    public void glMaterialfv(
-        int face,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glMaterialfv(
-        int face,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glMaterialx(
-        int face,
-        int pname,
-        int param
-    ){}
-
-    public void glMaterialxv(
-        int face,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glMaterialxv(
-        int face,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glMatrixMode(
-        int mode
-    ){}
-
-    public void glMultMatrixf(
-        float[] m,
-        int offset
-    ){}
-
-    public void glMultMatrixf(
-        java.nio.FloatBuffer m
-    ){}
-
-    public void glMultMatrixx(
-        int[] m,
-        int offset
-    ){}
-
-    public void glMultMatrixx(
-        java.nio.IntBuffer m
-    ){}
-
-    public void glMultiTexCoord4f(
-        int target,
-        float s,
-        float t,
-        float r,
-        float q
-    ){}
-
-    public void glMultiTexCoord4x(
-        int target,
-        int s,
-        int t,
-        int r,
-        int q
-    ){}
-
-    public void glNormal3f(
-        float nx,
-        float ny,
-        float nz
-    ){}
-
-    public void glNormal3x(
-        int nx,
-        int ny,
-        int nz
-    ){}
-
-    public void glNormalPointer(
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glOrthof(
-        float left,
-        float right,
-        float bottom,
-        float top,
-        float zNear,
-        float zFar
-    ){}
-
-    public void glOrthox(
-        int left,
-        int right,
-        int bottom,
-        int top,
-        int zNear,
-        int zFar
-    ){}
-
-    public void glPixelStorei(
-        int pname,
-        int param
-    ){}
-
-    public void glPointSize(
-        float size
-    ){}
-
-    public void glPointSizex(
-        int size
-    ){}
-
-    public void glPolygonOffset(
-        float factor,
-        float units
-    ){}
-
-    public void glPolygonOffsetx(
-        int factor,
-        int units
-    ){}
-
-    public void glPopMatrix(
-    ){}
-
-    public void glPushMatrix(
-    ){}
-
-    public void glReadPixels(
-        int x,
-        int y,
-        int width,
-        int height,
-        int format,
-        int type,
-        java.nio.Buffer pixels
-    ){}
-
-    public void glRotatef(
-        float angle,
-        float x,
-        float y,
-        float z
-    ){}
-
-    public void glRotatex(
-        int angle,
-        int x,
-        int y,
-        int z
-    ){}
-
-    public void glSampleCoverage(
-        float value,
-        boolean invert
-    ){}
-
-    public void glSampleCoveragex(
-        int value,
-        boolean invert
-    ){}
-
-    public void glScalef(
-        float x,
-        float y,
-        float z
-    ){}
-
-    public void glScalex(
-        int x,
-        int y,
-        int z
-    ){}
-
-    public void glScissor(
-        int x,
-        int y,
-        int width,
-        int height
-    ){}
-
-    public void glShadeModel(
-        int mode
-    ){}
-
-    public void glStencilFunc(
-        int func,
-        int ref,
-        int mask
-    ){}
-
-    public void glStencilMask(
-        int mask
-    ){}
-
-    public void glStencilOp(
-        int fail,
-        int zfail,
-        int zpass
-    ){}
-
-    public void glTexCoordPointer(
-        int size,
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glTexEnvf(
-        int target,
-        int pname,
-        float param
-    ){}
-
-    public void glTexEnvfv(
-        int target,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glTexEnvfv(
-        int target,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glTexEnvx(
-        int target,
-        int pname,
-        int param
-    ){}
-
-    public void glTexEnvxv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexEnvxv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glTexImage2D(
-        int target,
-        int level,
-        int internalformat,
-        int width,
-        int height,
-        int border,
-        int format,
-        int type,
-        java.nio.Buffer pixels
-    ){}
-
-    public void glTexParameterf(
-        int target,
-        int pname,
-        float param
-    ){}
-
-    public void glTexParameterx(
-        int target,
-        int pname,
-        int param
-    ){}
-
-    public void glTexSubImage2D(
-        int target,
-        int level,
-        int xoffset,
-        int yoffset,
-        int width,
-        int height,
-        int format,
-        int type,
-        java.nio.Buffer pixels
-    ){}
-
-    public void glTranslatef(
-        float x,
-        float y,
-        float z
-    ){}
-
-    public void glTranslatex(
-        int x,
-        int y,
-        int z
-    ){}
-
-    public void glVertexPointer(
-        int size,
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glViewport(
-        int x,
-        int y,
-        int width,
-        int height
-    ){}
-
-    public int glQueryMatrixxOES(
-        int[] mantissa,
-        int mantissaOffset,
-        int[] exponent,
-        int exponentOffset
-    ){ throw new UnsupportedOperationException(); }
-
-    public int glQueryMatrixxOES(
-        java.nio.IntBuffer mantissa,
-        java.nio.IntBuffer exponent
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glGetPointerv(int pname, java.nio.Buffer[] params){}
-    public void glBindBuffer(
-        int target,
-        int buffer
-    ){}
-
-    public void glBufferData(
-        int target,
-        int size,
-        java.nio.Buffer data,
-        int usage
-    ){}
-
-    public void glBufferSubData(
-        int target,
-        int offset,
-        int size,
-        java.nio.Buffer data
-    ){}
-
-    public void glClipPlanef(
-        int plane,
-        float[] equation,
-        int offset
-    ){}
-
-    public void glClipPlanef(
-        int plane,
-        java.nio.FloatBuffer equation
-    ){}
-
-    public void glClipPlanex(
-        int plane,
-        int[] equation,
-        int offset
-    ){}
-
-    public void glClipPlanex(
-        int plane,
-        java.nio.IntBuffer equation
-    ){}
-
-    public void glColor4ub(
-        byte red,
-        byte green,
-        byte blue,
-        byte alpha
-    ){}
-
-    public void glColorPointer(
-        int size,
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glDeleteBuffers(
-        int n,
-        int[] buffers,
-        int offset
-    ){}
-
-    public void glDeleteBuffers(
-        int n,
-        java.nio.IntBuffer buffers
-    ){}
-
-    public void glDrawElements(
-        int mode,
-        int count,
-        int type,
-        int offset
-    ){}
-
-    public void glGenBuffers(
-        int n,
-        int[] buffers,
-        int offset
-    ){}
-
-    public void glGenBuffers(
-        int n,
-        java.nio.IntBuffer buffers
-    ){}
-
-    public void glGetBooleanv(
-        int pname,
-        boolean[] params,
-        int offset
-    ){}
-
-    public void glGetBooleanv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetBufferParameteriv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetBufferParameteriv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetClipPlanef(
-        int pname,
-        float[] eqn,
-        int offset
-    ){}
-
-    public void glGetClipPlanef(
-        int pname,
-        java.nio.FloatBuffer eqn
-    ){}
-
-    public void glGetClipPlanex(
-        int pname,
-        int[] eqn,
-        int offset
-    ){}
-
-    public void glGetClipPlanex(
-        int pname,
-        java.nio.IntBuffer eqn
-    ){}
-
-    public void glGetFixedv(
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetFixedv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetFloatv(
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glGetFloatv(
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glGetLightfv(
-        int light,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glGetLightfv(
-        int light,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glGetLightxv(
-        int light,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetLightxv(
-        int light,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetMaterialfv(
-        int face,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glGetMaterialfv(
-        int face,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glGetMaterialxv(
-        int face,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetMaterialxv(
-        int face,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexEnviv(
-        int env,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexEnviv(
-        int env,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexEnvxv(
-        int env,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexEnvxv(
-        int env,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexParameterfv(
-        int target,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glGetTexParameterfv(
-        int target,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glGetTexParameteriv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexParameteriv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexParameterxv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexParameterxv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public boolean glIsBuffer(
-        int buffer
-    ){ throw new UnsupportedOperationException(); }
-
-    public boolean glIsEnabled(
-        int cap
-    ){ throw new UnsupportedOperationException(); }
-
-    public boolean glIsTexture(
-        int texture
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glNormalPointer(
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glPointParameterf(
-        int pname,
-        float param
-    ){}
-
-    public void glPointParameterfv(
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glPointParameterfv(
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glPointParameterx(
-        int pname,
-        int param
-    ){}
-
-    public void glPointParameterxv(
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glPointParameterxv(
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glPointSizePointerOES(
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glTexCoordPointer(
-        int size,
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glTexEnvi(
-        int target,
-        int pname,
-        int param
-    ){}
-
-    public void glTexEnviv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexEnviv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glTexParameterfv(
-        int target,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glTexParameterfv(
-        int target,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glTexParameteri(
-        int target,
-        int pname,
-        int param
-    ){}
-
-    public void glTexParameteriv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexParameteriv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glTexParameterxv(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexParameterxv(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glVertexPointer(
-        int size,
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glCurrentPaletteMatrixOES(
-        int matrixpaletteindex
-    ){}
-
-    public void glDrawTexfOES(
-        float x,
-        float y,
-        float z,
-        float width,
-        float height
-    ){}
-
-    public void glDrawTexfvOES(
-        float[] coords,
-        int offset
-    ){}
-
-    public void glDrawTexfvOES(
-        java.nio.FloatBuffer coords
-    ){}
-
-    public void glDrawTexiOES(
-        int x,
-        int y,
-        int z,
-        int width,
-        int height
-    ){}
-
-    public void glDrawTexivOES(
-        int[] coords,
-        int offset
-    ){}
-
-    public void glDrawTexivOES(
-        java.nio.IntBuffer coords
-    ){}
-
-    public void glDrawTexsOES(
-        short x,
-        short y,
-        short z,
-        short width,
-        short height
-    ){}
-
-    public void glDrawTexsvOES(
-        short[] coords,
-        int offset
-    ){}
-
-    public void glDrawTexsvOES(
-        java.nio.ShortBuffer coords
-    ){}
-
-    public void glDrawTexxOES(
-        int x,
-        int y,
-        int z,
-        int width,
-        int height
-    ){}
-
-    public void glDrawTexxvOES(
-        int[] coords,
-        int offset
-    ){}
-
-    public void glDrawTexxvOES(
-        java.nio.IntBuffer coords
-    ){}
-
-    public void glLoadPaletteFromModelViewMatrixOES(
-    ){}
-
-    public void glMatrixIndexPointerOES(
-        int size,
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glMatrixIndexPointerOES(
-        int size,
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glWeightPointerOES(
-        int size,
-        int type,
-        int stride,
-        java.nio.Buffer pointer
-    ){}
-
-    public void glWeightPointerOES(
-        int size,
-        int type,
-        int stride,
-        int offset
-    ){}
-
-    public void glBindFramebufferOES(
-        int target,
-        int framebuffer
-    ){}
-
-    public void glBindRenderbufferOES(
-        int target,
-        int renderbuffer
-    ){}
-
-    public void glBlendEquation(
-        int mode
-    ){}
-
-    public void glBlendEquationSeparate(
-        int modeRGB,
-        int modeAlpha
-    ){}
-
-    public void glBlendFuncSeparate(
-        int srcRGB,
-        int dstRGB,
-        int srcAlpha,
-        int dstAlpha
-    ){}
-
-    public int glCheckFramebufferStatusOES(
-        int target
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glDeleteFramebuffersOES(
-        int n,
-        int[] framebuffers,
-        int offset
-    ){}
-
-    public void glDeleteFramebuffersOES(
-        int n,
-        java.nio.IntBuffer framebuffers
-    ){}
-
-    public void glDeleteRenderbuffersOES(
-        int n,
-        int[] renderbuffers,
-        int offset
-    ){}
-
-    public void glDeleteRenderbuffersOES(
-        int n,
-        java.nio.IntBuffer renderbuffers
-    ){}
-
-    public void glFramebufferRenderbufferOES(
-        int target,
-        int attachment,
-        int renderbuffertarget,
-        int renderbuffer
-    ){}
-
-    public void glFramebufferTexture2DOES(
-        int target,
-        int attachment,
-        int textarget,
-        int texture,
-        int level
-    ){}
-
-    public void glGenerateMipmapOES(
-        int target
-    ){}
-
-    public void glGenFramebuffersOES(
-        int n,
-        int[] framebuffers,
-        int offset
-    ){}
-
-    public void glGenFramebuffersOES(
-        int n,
-        java.nio.IntBuffer framebuffers
-    ){}
-
-    public void glGenRenderbuffersOES(
-        int n,
-        int[] renderbuffers,
-        int offset
-    ){}
-
-    public void glGenRenderbuffersOES(
-        int n,
-        java.nio.IntBuffer renderbuffers
-    ){}
-
-    public void glGetFramebufferAttachmentParameterivOES(
-        int target,
-        int attachment,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetFramebufferAttachmentParameterivOES(
-        int target,
-        int attachment,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetRenderbufferParameterivOES(
-        int target,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetRenderbufferParameterivOES(
-        int target,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexGenfv(
-        int coord,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glGetTexGenfv(
-        int coord,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glGetTexGeniv(
-        int coord,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexGeniv(
-        int coord,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glGetTexGenxv(
-        int coord,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glGetTexGenxv(
-        int coord,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public boolean glIsFramebufferOES(
-        int framebuffer
-    ){ throw new UnsupportedOperationException(); }
-
-    public boolean glIsRenderbufferOES(
-        int renderbuffer
-    ){ throw new UnsupportedOperationException(); }
-
-    public void glRenderbufferStorageOES(
-        int target,
-        int internalformat,
-        int width,
-        int height
-    ){}
-
-    public void glTexGenf(
-        int coord,
-        int pname,
-        float param
-    ){}
-
-    public void glTexGenfv(
-        int coord,
-        int pname,
-        float[] params,
-        int offset
-    ){}
-
-    public void glTexGenfv(
-        int coord,
-        int pname,
-        java.nio.FloatBuffer params
-    ){}
-
-    public void glTexGeni(
-        int coord,
-        int pname,
-        int param
-    ){}
-
-    public void glTexGeniv(
-        int coord,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexGeniv(
-        int coord,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-
-    public void glTexGenx(
-        int coord,
-        int pname,
-        int param
-    ){}
-
-    public void glTexGenxv(
-        int coord,
-        int pname,
-        int[] params,
-        int offset
-    ){}
-
-    public void glTexGenxv(
-        int coord,
-        int pname,
-        java.nio.IntBuffer params
-    ){}
-}
diff --git a/tests/src/com/android/gallery3d/ui/GLViewMock.java b/tests/src/com/android/gallery3d/ui/GLViewMock.java
index 7b941da..9b7488f 100644
--- a/tests/src/com/android/gallery3d/ui/GLViewMock.java
+++ b/tests/src/com/android/gallery3d/ui/GLViewMock.java
@@ -16,6 +16,8 @@
 
 package com.android.gallery3d.ui;
 
+import com.android.gallery3d.glrenderer.GLCanvas;
+
 class GLViewMock extends GLView {
     // onAttachToRoot
     int mOnAttachCalled;
diff --git a/tests/src/com/android/gallery3d/ui/TextureTest.java b/tests/src/com/android/gallery3d/ui/TextureTest.java
deleted file mode 100644
index 36446b3..0000000
--- a/tests/src/com/android/gallery3d/ui/TextureTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2010 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.gallery3d.ui;
-
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.TestCase;
-
-import javax.microedition.khronos.opengles.GL11;
-
-@SmallTest
-public class TextureTest extends TestCase {
-    @SuppressWarnings("unused")
-    private static final String TAG = "TextureTest";
-
-    class MyBasicTexture extends BasicTexture {
-        int mOnBindCalled;
-        int mOpaqueCalled;
-
-        MyBasicTexture(GLCanvas canvas, int id) {
-            super(canvas, id, 0);
-        }
-
-        @Override
-        protected boolean onBind(GLCanvas canvas) {
-            mOnBindCalled++;
-            return true;
-        }
-
-        @Override
-        protected int getTarget() {
-            return GL11.GL_TEXTURE_2D;
-        }
-
-        public boolean isOpaque() {
-            mOpaqueCalled++;
-            return true;
-        }
-
-        void upload() {
-            mState = STATE_LOADED;
-        }
-    }
-
-    @SmallTest
-    public void testBasicTexture() {
-        GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
-        MyBasicTexture texture = new MyBasicTexture(canvas, 47);
-
-        assertEquals(47, texture.getId());
-        texture.setSize(1, 1);
-        assertEquals(1, texture.getWidth());
-        assertEquals(1, texture.getHeight());
-        assertEquals(1, texture.getTextureWidth());
-        assertEquals(1, texture.getTextureHeight());
-        texture.setSize(3, 5);
-        assertEquals(3, texture.getWidth());
-        assertEquals(5, texture.getHeight());
-        assertEquals(4, texture.getTextureWidth());
-        assertEquals(8, texture.getTextureHeight());
-
-        assertFalse(texture.isLoaded());
-        texture.upload();
-        assertTrue(texture.isLoaded());
-
-        // For a different GL, it's not loaded.
-        GLCanvas canvas2 = new GLCanvasImpl(new GLStub());
-        assertFalse(texture.isLoaded());
-
-        assertEquals(0, texture.mOnBindCalled);
-        assertEquals(0, texture.mOpaqueCalled);
-        texture.draw(canvas, 100, 200, 1, 1);
-        assertEquals(1, texture.mOnBindCalled);
-        assertEquals(1, texture.mOpaqueCalled);
-        texture.draw(canvas, 0, 0);
-        assertEquals(2, texture.mOnBindCalled);
-        assertEquals(2, texture.mOpaqueCalled);
-    }
-
-    @SmallTest
-    public void testColorTexture() {
-        GLCanvasMock canvas = new GLCanvasMock();
-        ColorTexture texture = new ColorTexture(0x12345678);
-
-        texture.setSize(42, 47);
-        assertEquals(texture.getWidth(), 42);
-        assertEquals(texture.getHeight(), 47);
-        assertEquals(0, canvas.mFillRectCalled);
-        texture.draw(canvas, 0, 0);
-        assertEquals(1, canvas.mFillRectCalled);
-        assertEquals(0x12345678, canvas.mFillRectColor);
-        assertEquals(42f, canvas.mFillRectWidth);
-        assertEquals(47f, canvas.mFillRectHeight);
-        assertFalse(texture.isOpaque());
-        assertTrue(new ColorTexture(0xFF000000).isOpaque());
-    }
-
-    private class MyUploadedTexture extends UploadedTexture {
-        int mGetCalled;
-        int mFreeCalled;
-        Bitmap mBitmap;
-        @Override
-        protected Bitmap onGetBitmap() {
-            mGetCalled++;
-            Config config = Config.ARGB_8888;
-            mBitmap = Bitmap.createBitmap(47, 42, config);
-            return mBitmap;
-        }
-        @Override
-        protected void onFreeBitmap(Bitmap bitmap) {
-            mFreeCalled++;
-            assertSame(mBitmap, bitmap);
-            mBitmap.recycle();
-            mBitmap = null;
-        }
-    }
-
-    @SmallTest
-    public void testUploadedTexture() {
-        GL11 glStub = new GLStub();
-        GLCanvas canvas = new GLCanvasImpl(glStub);
-        MyUploadedTexture texture = new MyUploadedTexture();
-
-        // draw it and the bitmap should be fetched.
-        assertEquals(0, texture.mFreeCalled);
-        assertEquals(0, texture.mGetCalled);
-        texture.draw(canvas, 0, 0);
-        assertEquals(1, texture.mGetCalled);
-        assertTrue(texture.isLoaded());
-        assertTrue(texture.isContentValid());
-
-        // invalidate content and it should be freed.
-        texture.invalidateContent();
-        assertFalse(texture.isContentValid());
-        assertEquals(1, texture.mFreeCalled);
-        assertTrue(texture.isLoaded());  // But it's still loaded
-
-        // draw it again and the bitmap should be fetched again.
-        texture.draw(canvas, 0, 0);
-        assertEquals(2, texture.mGetCalled);
-        assertTrue(texture.isLoaded());
-        assertTrue(texture.isContentValid());
-
-        // recycle the texture and it should be freed again.
-        texture.recycle();
-        assertEquals(2, texture.mFreeCalled);
-        // TODO: these two are broken and waiting for fix.
-        //assertFalse(texture.isLoaded(canvas));
-        //assertFalse(texture.isContentValid(canvas));
-    }
-
-    class MyTextureForMixed extends BasicTexture {
-        MyTextureForMixed(GLCanvas canvas, int id) {
-            super(canvas, id, 0);
-        }
-
-        @Override
-        protected boolean onBind(GLCanvas canvas) {
-            return true;
-        }
-
-        @Override
-        protected int getTarget() {
-            return GL11.GL_TEXTURE_2D;
-        }
-
-        public boolean isOpaque() {
-            return true;
-        }
-    }
-
-    @SmallTest
-    public void testBitmapTexture() {
-        Config config = Config.ARGB_8888;
-        Bitmap bitmap = Bitmap.createBitmap(47, 42, config);
-        assertFalse(bitmap.isRecycled());
-        BitmapTexture texture = new BitmapTexture(bitmap);
-        texture.recycle();
-        assertFalse(bitmap.isRecycled());
-        bitmap.recycle();
-        assertTrue(bitmap.isRecycled());
-    }
-}
diff --git a/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java b/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java
new file mode 100644
index 0000000..b8fb05f
--- /dev/null
+++ b/tests/src/com/android/gallery3d/unittest/CameraUnitTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 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.gallery3d.unittest;
+
+import com.android.camera.Util;
+
+import android.graphics.Matrix;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class CameraUnitTest extends TestCase {
+    public void testRoundOrientation() {
+        int h = Util.ORIENTATION_HYSTERESIS;
+        assertEquals(0, Util.roundOrientation(0, 0));
+        assertEquals(0, Util.roundOrientation(359, 0));
+        assertEquals(0, Util.roundOrientation(0 + 44 + h, 0));
+        assertEquals(90, Util.roundOrientation(0 + 45 + h, 0));
+        assertEquals(0, Util.roundOrientation(360 - 44 - h, 0));
+        assertEquals(270, Util.roundOrientation(360 - 45 - h, 0));
+
+        assertEquals(90, Util.roundOrientation(90, 90));
+        assertEquals(90, Util.roundOrientation(90 + 44 + h, 90));
+        assertEquals(180, Util.roundOrientation(90 + 45 + h, 90));
+        assertEquals(90, Util.roundOrientation(90 - 44 - h, 90));
+        assertEquals(0, Util.roundOrientation(90 - 45 - h, 90));
+
+        assertEquals(180, Util.roundOrientation(180, 180));
+        assertEquals(180, Util.roundOrientation(180 + 44 + h, 180));
+        assertEquals(270, Util.roundOrientation(180 + 45 + h, 180));
+        assertEquals(180, Util.roundOrientation(180 - 44 - h, 180));
+        assertEquals(90, Util.roundOrientation(180 - 45 - h, 180));
+
+        assertEquals(270, Util.roundOrientation(270, 270));
+        assertEquals(270, Util.roundOrientation(270 + 44 + h, 270));
+        assertEquals(0, Util.roundOrientation(270 + 45 + h, 270));
+        assertEquals(270, Util.roundOrientation(270 - 44 - h, 270));
+        assertEquals(180, Util.roundOrientation(270 - 45 - h, 270));
+
+        assertEquals(90, Util.roundOrientation(90, 0));
+        assertEquals(180, Util.roundOrientation(180, 0));
+        assertEquals(270, Util.roundOrientation(270, 0));
+
+        assertEquals(0, Util.roundOrientation(0, 90));
+        assertEquals(180, Util.roundOrientation(180, 90));
+        assertEquals(270, Util.roundOrientation(270, 90));
+
+        assertEquals(0, Util.roundOrientation(0, 180));
+        assertEquals(90, Util.roundOrientation(90, 180));
+        assertEquals(270, Util.roundOrientation(270, 180));
+
+        assertEquals(0, Util.roundOrientation(0, 270));
+        assertEquals(90, Util.roundOrientation(90, 270));
+        assertEquals(180, Util.roundOrientation(180, 270));
+    }
+
+    public void testPrepareMatrix() {
+        Matrix matrix = new Matrix();
+        float[] points;
+        int[] expected;
+
+        Util.prepareMatrix(matrix, false, 0, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {0, 0, 400, 240, 800, 480, 400, 480, 100, 300};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, false, 90, 800, 480);
+        points = new float[] {-1000, -1000,   0,   0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {800, 0, 400, 240, 0, 480, 0, 240, 300, 60};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, false, 180, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {800, 480, 400, 240, 0, 0, 400, 0, 700, 180};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, true, 180, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {0, 480, 400, 240, 800, 0, 400, 0, 100, 180};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+    }
+
+    private void assertEquals(int expected[], float[] actual) {
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Array index " + i + " mismatch", expected[i], Math.round(actual[i]));
+        }
+    }
+}
diff --git a/tests/src/com/android/photos/data/DataTestRunner.java b/tests/src/com/android/photos/data/DataTestRunner.java
new file mode 100644
index 0000000..10618d6
--- /dev/null
+++ b/tests/src/com/android/photos/data/DataTestRunner.java
@@ -0,0 +1,46 @@
+/*
+ * 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.photos.data;
+
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import com.android.photos.data.TestHelper.TestInitialization;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class DataTestRunner extends InstrumentationTestRunner {
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(PhotoDatabaseTest.class);
+        suite.addTestSuite(PhotoProviderTest.class);
+        TestHelper.addTests(MediaCacheTest.class, suite, new TestInitialization() {
+            @Override
+            public void initialize(TestCase testCase) {
+                MediaCacheTest test = (MediaCacheTest) testCase;
+                test.setLocalContext(getContext());
+            }
+        });
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return DataTestRunner.class.getClassLoader();
+    }
+}
diff --git a/tests/src/com/android/photos/data/MediaCacheTest.java b/tests/src/com/android/photos/data/MediaCacheTest.java
new file mode 100644
index 0000000..9e71128
--- /dev/null
+++ b/tests/src/com/android/photos/data/MediaCacheTest.java
@@ -0,0 +1,388 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.SystemClock;
+import android.test.ProviderTestCase2;
+
+import com.android.gallery3d.tests.R;
+import com.android.photos.data.MediaCache.ImageReady;
+import com.android.photos.data.MediaCache.OriginalReady;
+import com.android.photos.data.MediaRetriever.MediaSize;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MediaCacheTest extends ProviderTestCase2<PhotoProvider> {
+    @SuppressWarnings("unused")
+    private static final String TAG = MediaCacheTest.class.getSimpleName();
+
+    private File mDir;
+    private File mImage;
+    private File mCacheDir;
+    private Resources mResources;
+    private MediaCache mMediaCache;
+    private ReadyCollector mReady;
+
+    public static final long MAX_WAIT = 2000;
+
+    private static class ReadyCollector implements ImageReady, OriginalReady {
+        public File mOriginalFile;
+        public InputStream mInputStream;
+
+        @Override
+        public synchronized void originalReady(File originalFile) {
+            mOriginalFile = originalFile;
+            notifyAll();
+        }
+
+        @Override
+        public synchronized void imageReady(InputStream bitmapInputStream) {
+            mInputStream = bitmapInputStream;
+            notifyAll();
+        }
+
+        public synchronized boolean waitForNotification() {
+            long endWait = SystemClock.uptimeMillis() + MAX_WAIT;
+
+            try {
+                while (mInputStream == null && mOriginalFile == null
+                        && SystemClock.uptimeMillis() < endWait) {
+                    wait(endWait - SystemClock.uptimeMillis());
+                }
+            } catch (InterruptedException e) {
+            }
+            return mInputStream != null || mOriginalFile != null;
+        }
+    }
+
+    private static class DummyMediaRetriever implements MediaRetriever {
+        private boolean mNullUri = false;
+        @Override
+        public File getLocalFile(Uri contentUri) {
+            return null;
+        }
+
+        @Override
+        public MediaSize getFastImageSize(Uri contentUri, MediaSize size) {
+            return null;
+        }
+
+        @Override
+        public byte[] getTemporaryImage(Uri contentUri, MediaSize temporarySize) {
+            return null;
+        }
+
+        @Override
+        public boolean getMedia(Uri contentUri, MediaSize imageSize, File tempFile) {
+            return false;
+        }
+
+        @Override
+        public Uri normalizeUri(Uri contentUri, MediaSize size) {
+            if (mNullUri) {
+                return null;
+            } else {
+                return contentUri;
+            }
+        }
+
+        @Override
+        public MediaSize normalizeMediaSize(Uri contentUri, MediaSize size) {
+            return size;
+        }
+
+        public void setNullUri() {
+            mNullUri = true;
+        }
+    };
+
+    public MediaCacheTest() {
+        super(PhotoProvider.class, PhotoProvider.AUTHORITY);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mReady = new ReadyCollector();
+        File externalDir = Environment.getExternalStorageDirectory();
+        mDir = new File(externalDir, "test");
+        mDir.mkdirs();
+        mCacheDir = new File(externalDir, "test_cache");
+        mImage = new File(mDir, "original.jpg");
+        MediaCache.initialize(getMockContext());
+        MediaCache.getInstance().setCacheDir(mCacheDir);
+        mMediaCache = MediaCache.getInstance();
+        mMediaCache.addRetriever("file", "", new FileRetriever());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mMediaCache.clearCacheDir();
+        MediaCache.shutdown();
+        mMediaCache = null;
+        mImage.delete();
+        mDir.delete();
+        mCacheDir.delete();
+    }
+
+    public void setLocalContext(Context context) {
+        mResources = context.getResources();
+    }
+
+    public void testRetrieveOriginal() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        mMediaCache.retrieveOriginal(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNull(mReady.mInputStream);
+        assertEquals(mImage, mReady.mOriginalFile);
+    }
+
+    public void testRetrievePreview() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        mMediaCache.retrievePreview(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNotNull(mReady.mInputStream);
+        assertNull(mReady.mOriginalFile);
+        Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+        mReady.mInputStream.close();
+        assertNotNull(bitmap);
+        Bitmap original = BitmapFactory.decodeFile(mImage.getPath());
+        assertTrue(bitmap.getWidth() < original.getWidth());
+        assertTrue(bitmap.getHeight() < original.getHeight());
+        int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+        int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview);
+        assertTrue(maxDimension >= targetSize);
+        assertTrue(maxDimension < (targetSize * 2));
+    }
+
+    public void testRetrieveExifThumb() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        ReadyCollector done = new ReadyCollector();
+        mMediaCache.retrieveThumbnail(uri, done, mReady);
+        assertTrue(mReady.waitForNotification());
+        assertNotNull(mReady.mInputStream);
+        assertNull(mReady.mOriginalFile);
+        Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+        mReady.mInputStream.close();
+        assertTrue(done.waitForNotification());
+        assertNotNull(done.mInputStream);
+        done.mInputStream.close();
+        assertNotNull(bitmap);
+        assertEquals(320, bitmap.getWidth());
+        assertEquals(240, bitmap.getHeight());
+    }
+
+    public void testRetrieveThumb() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        long downsampleStart = SystemClock.uptimeMillis();
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        long downsampleEnd = SystemClock.uptimeMillis();
+        assertNotNull(mReady.mInputStream);
+        assertNull(mReady.mOriginalFile);
+        Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+        mReady.mInputStream.close();
+        assertNotNull(bitmap);
+        Bitmap original = BitmapFactory.decodeFile(mImage.getPath());
+        assertTrue(bitmap.getWidth() < original.getWidth());
+        assertTrue(bitmap.getHeight() < original.getHeight());
+        int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+        int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail);
+        assertTrue(maxDimension >= targetSize);
+        assertTrue(maxDimension < (targetSize * 2));
+
+        // Retrieve cached thumb.
+        mReady = new ReadyCollector();
+        long start = SystemClock.uptimeMillis();
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        mReady.mInputStream.close();
+        long end = SystemClock.uptimeMillis();
+        // Already cached. Wait shorter time.
+        assertTrue((end - start) < (downsampleEnd - downsampleStart) / 2);
+    }
+
+    public void testGetVideo() throws IOException {
+        mImage = new File(mDir, "original.mp4");
+        copyResourceToFile(R.raw.android_lawn, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+
+        mMediaCache.retrieveOriginal(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNull(mReady.mInputStream);
+        assertNotNull(mReady.mOriginalFile);
+
+        mReady = new ReadyCollector();
+        mMediaCache.retrievePreview(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNotNull(mReady.mInputStream);
+        assertNull(mReady.mOriginalFile);
+        Bitmap bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+        mReady.mInputStream.close();
+        int maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+        int targetSize = MediaCacheUtils.getTargetSize(MediaSize.Preview);
+        assertTrue(maxDimension >= targetSize);
+        assertTrue(maxDimension < (targetSize * 2));
+
+        mReady = new ReadyCollector();
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNotNull(mReady.mInputStream);
+        assertNull(mReady.mOriginalFile);
+        bitmap = BitmapFactory.decodeStream(mReady.mInputStream);
+        mReady.mInputStream.close();
+        maxDimension = Math.max(bitmap.getWidth(), bitmap.getHeight());
+        targetSize = MediaCacheUtils.getTargetSize(MediaSize.Thumbnail);
+        assertTrue(maxDimension >= targetSize);
+        assertTrue(maxDimension < (targetSize * 2));
+    }
+
+    public void testFastImage() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        mReady.waitForNotification();
+        mReady.mInputStream.close();
+
+        mMediaCache.retrieveOriginal(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        assertNotNull(mReady.mInputStream);
+        mReady.mInputStream.close();
+    }
+
+    public void testBadRetriever() {
+        Uri uri = Photos.CONTENT_URI;
+        try {
+            mMediaCache.retrieveOriginal(uri, mReady, null);
+            fail("Expected exception");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    public void testInsertIntoCache() throws IOException {
+        // FileRetriever inserts into the cache opportunistically with Videos
+        mImage = new File(mDir, "original.mp4");
+        copyResourceToFile(R.raw.android_lawn, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        mReady.mInputStream.close();
+        assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+    }
+
+    public void testBadNormalizedUri() {
+        DummyMediaRetriever retriever = new DummyMediaRetriever();
+        Uri uri = Uri.fromParts("http", "world", "morestuff");
+        mMediaCache.addRetriever(uri.getScheme(), uri.getAuthority(), retriever);
+        retriever.setNullUri();
+        try {
+            mMediaCache.retrieveOriginal(uri, mReady, null);
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    public void testClearOldCache() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri uri = Uri.fromFile(mImage);
+        mMediaCache.retrievePreview(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        mReady.mInputStream.close();
+        mMediaCache.setMaxCacheSize(mMediaCache.getCachedFile(uri, MediaSize.Preview).length());
+        assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+
+        mReady = new ReadyCollector();
+        // This should kick the preview image out of the cache.
+        mMediaCache.retrieveThumbnail(uri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+        mReady.mInputStream.close();
+        assertNull(mMediaCache.getCachedFile(uri, MediaSize.Preview));
+        assertNotNull(mMediaCache.getCachedFile(uri, MediaSize.Thumbnail));
+    }
+
+    public void testClearLargeInCache() throws IOException {
+        copyResourceToFile(R.raw.galaxy_nexus, mImage.getPath());
+        Uri imageUri = Uri.fromFile(mImage);
+        mMediaCache.retrieveThumbnail(imageUri, mReady, null);
+        assertTrue(mReady.waitForNotification());
+            mReady.mInputStream.close();
+        assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+        long thumbSize = mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail).length();
+        mMediaCache.setMaxCacheSize(thumbSize * 10);
+
+        for (int i = 0; i < 9; i++) {
+            File tempImage = new File(mDir, "image" + i + ".jpg");
+            mImage.renameTo(tempImage);
+            Uri tempImageUri = Uri.fromFile(tempImage);
+            mReady = new ReadyCollector();
+            mMediaCache.retrieveThumbnail(tempImageUri, mReady, null);
+            assertTrue(mReady.waitForNotification());
+                mReady.mInputStream.close();
+            tempImage.renameTo(mImage);
+        }
+        assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+
+        for (int i = 0; i < 9; i++) {
+            File tempImage = new File(mDir, "image" + i + ".jpg");
+            mImage.renameTo(tempImage);
+            Uri tempImageUri = Uri.fromFile(tempImage);
+            mReady = new ReadyCollector();
+            mMediaCache.retrievePreview(tempImageUri, mReady, null);
+            assertTrue(mReady.waitForNotification());
+                mReady.mInputStream.close();
+            tempImage.renameTo(mImage);
+        }
+        assertNotNull(mMediaCache.getCachedFile(imageUri, MediaSize.Thumbnail));
+        Uri oldestUri = Uri.fromFile(new File(mDir, "image0.jpg"));
+        assertNull(mMediaCache.getCachedFile(oldestUri, MediaSize.Thumbnail));
+    }
+
+    private void copyResourceToFile(int resourceId, String path) throws IOException {
+        File outputDir = new File(path).getParentFile();
+        outputDir.mkdirs();
+
+        InputStream in = mResources.openRawResource(resourceId);
+        FileOutputStream out = new FileOutputStream(path);
+        byte[] buffer = new byte[1000];
+        int bytesRead;
+
+        while ((bytesRead = in.read(buffer)) >= 0) {
+            out.write(buffer, 0, bytesRead);
+        }
+
+        in.close();
+        out.close();
+    }
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseTest.java b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
new file mode 100644
index 0000000..e7c1689
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.photos.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.test.InstrumentationTestCase;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.io.File;
+import java.io.IOException;
+
+public class PhotoDatabaseTest extends InstrumentationTestCase {
+
+    private PhotoDatabase mDBHelper;
+    private static final String DB_NAME = "dummy.db";
+    private static final long PARENT_ID1 = 100;
+    private static final long PARENT_ID2 = 101;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Context context = getInstrumentation().getTargetContext();
+        context.deleteDatabase(DB_NAME);
+        mDBHelper = new PhotoDatabase(context, DB_NAME);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDBHelper.close();
+        mDBHelper = null;
+        Context context = getInstrumentation().getTargetContext();
+        context.deleteDatabase(DB_NAME);
+        super.tearDown();
+    }
+
+    public void testCreateDatabase() throws IOException {
+        Context context = getInstrumentation().getTargetContext();
+        File dbFile = context.getDatabasePath(DB_NAME);
+        SQLiteDatabase db = getReadableDB();
+        db.beginTransaction();
+        db.endTransaction();
+        assertTrue(dbFile.exists());
+    }
+
+    public void testTables() {
+        validateTable(Metadata.TABLE, PhotoDatabaseUtils.PROJECTION_METADATA);
+        validateTable(Albums.TABLE, PhotoDatabaseUtils.PROJECTION_ALBUMS);
+        validateTable(Photos.TABLE, PhotoDatabaseUtils.PROJECTION_PHOTOS);
+    }
+
+    public void testAlbumsConstraints() {
+        SQLiteDatabase db = getWritableDB();
+        db.beginTransaction();
+        try {
+            long accountId = 100;
+            // Test NOT NULL constraint on name
+            assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, null, Albums.VISIBILITY_PRIVATE,
+                    accountId));
+
+            // test NOT NULL constraint on privacy
+            assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello", null, accountId));
+
+            // test NOT NULL constraint on account_id
+            assertFalse(PhotoDatabaseUtils.insertAlbum(db, null, "hello",
+                    Albums.VISIBILITY_PRIVATE, null));
+
+            // Normal insert
+            assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+                    Albums.VISIBILITY_PRIVATE, accountId));
+
+            long albumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID1);
+
+            // Assign a valid child
+            assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID2, "hello",
+                    Albums.VISIBILITY_PRIVATE, accountId));
+
+            long otherAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, PARENT_ID2);
+            assertNotSame(albumId, otherAlbumId);
+
+            // This is a valid child of another album.
+            assertTrue(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+                    Albums.VISIBILITY_PRIVATE, accountId));
+
+            // This isn't allowed due to uniqueness constraint (parent_id/name)
+            assertFalse(PhotoDatabaseUtils.insertAlbum(db, otherAlbumId, "hello",
+                    Albums.VISIBILITY_PRIVATE, accountId));
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void testPhotosConstraints() {
+        SQLiteDatabase db = getWritableDB();
+        db.beginTransaction();
+        try {
+            int width = 100;
+            int height = 100;
+            long dateTaken = System.currentTimeMillis();
+            String mimeType = "test/test";
+            long accountId = 100;
+
+            // Test NOT NULL mime-type
+            assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, null,
+                    accountId));
+
+            // Test NOT NULL width
+            assertFalse(PhotoDatabaseUtils.insertPhoto(db, null, height, dateTaken, null, mimeType,
+                    accountId));
+
+            // Test NOT NULL height
+            assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, null, dateTaken, null, mimeType,
+                    accountId));
+
+            // Test NOT NULL dateTaken
+            assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, null, null, mimeType,
+                    accountId));
+
+            // Test NOT NULL accountId
+            assertFalse(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null,
+                    mimeType, null));
+
+            // Test normal insert
+            assertTrue(PhotoDatabaseUtils.insertPhoto(db, width, height, dateTaken, null, mimeType,
+                    accountId));
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void testMetadataConstraints() {
+        SQLiteDatabase db = getWritableDB();
+        db.beginTransaction();
+        try {
+            final String mimeType = "test/test";
+            PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType, 100L);
+            long photoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, PARENT_ID1);
+
+            // Test NOT NULL PHOTO_ID constraint.
+            assertFalse(PhotoDatabaseUtils.insertMetadata(db, null, "foo", "bar"));
+
+            // Normal insert.
+            assertTrue(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "bar"));
+
+            // Test uniqueness constraint.
+            assertFalse(PhotoDatabaseUtils.insertMetadata(db, photoId, "foo", "baz"));
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void testAccountsConstraints() {
+        SQLiteDatabase db = getWritableDB();
+        db.beginTransaction();
+        try {
+            assertFalse(PhotoDatabaseUtils.insertAccount(db, null));
+            assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+            assertTrue(PhotoDatabaseUtils.insertAccount(db, "hello"));
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    public void testUpgrade() {
+        SQLiteDatabase db = getWritableDB();
+        db.beginTransaction();
+        try {
+            assertTrue(PhotoDatabaseUtils.insertAccount(db, "Hello"));
+            assertTrue(PhotoDatabaseUtils.insertAlbum(db, PARENT_ID1, "hello",
+                    Albums.VISIBILITY_PRIVATE, 100L));
+            final String mimeType = "test/test";
+            assertTrue(PhotoDatabaseUtils.insertPhoto(db, 100, 100, 100L, PARENT_ID1, mimeType,
+                    100L));
+            // Normal insert.
+            assertTrue(PhotoDatabaseUtils.insertMetadata(db, 100L, "foo", "bar"));
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        mDBHelper.close();
+        Context context = getInstrumentation().getTargetContext();
+        mDBHelper = new PhotoDatabase(context, DB_NAME, PhotoDatabase.DB_VERSION + 1);
+        db = getReadableDB();
+        assertEquals(0, DatabaseUtils.queryNumEntries(db, Accounts.TABLE));
+        assertEquals(0, DatabaseUtils.queryNumEntries(db, Photos.TABLE));
+        assertEquals(0, DatabaseUtils.queryNumEntries(db, Albums.TABLE));
+        assertEquals(0, DatabaseUtils.queryNumEntries(db, Metadata.TABLE));
+    }
+
+    private SQLiteDatabase getReadableDB() {
+        return mDBHelper.getReadableDatabase();
+    }
+
+    private SQLiteDatabase getWritableDB() {
+        return mDBHelper.getWritableDatabase();
+    }
+
+    private void validateTable(String table, String[] projection) {
+        SQLiteDatabase db = getReadableDB();
+        Cursor cursor = db.query(table, projection, null, null, null, null, null);
+        assertNotNull(cursor);
+        assertEquals(cursor.getCount(), 0);
+        assertEquals(cursor.getColumnCount(), projection.length);
+        for (int i = 0; i < projection.length; i++) {
+            assertEquals(cursor.getColumnName(i), projection[i]);
+        }
+    }
+
+
+}
diff --git a/tests/src/com/android/photos/data/PhotoDatabaseUtils.java b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
new file mode 100644
index 0000000..f7a46d4
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoDatabaseUtils.java
@@ -0,0 +1,135 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import junit.framework.AssertionFailedError;
+
+public class PhotoDatabaseUtils {
+    public static String[] PROJECTION_ALBUMS = {
+        Albums._ID,
+        Albums.ACCOUNT_ID,
+        Albums.PARENT_ID,
+        Albums.VISIBILITY,
+        Albums.LOCATION_STRING,
+        Albums.TITLE,
+        Albums.SUMMARY,
+        Albums.DATE_PUBLISHED,
+        Albums.DATE_MODIFIED,
+    };
+
+    public static String[] PROJECTION_METADATA = {
+        Metadata.PHOTO_ID,
+        Metadata.KEY,
+        Metadata.VALUE,
+    };
+
+    public static String[] PROJECTION_PHOTOS = {
+        Photos._ID,
+        Photos.ACCOUNT_ID,
+        Photos.WIDTH,
+        Photos.HEIGHT,
+        Photos.DATE_TAKEN,
+        Photos.ALBUM_ID,
+        Photos.MIME_TYPE,
+        Photos.TITLE,
+        Photos.DATE_MODIFIED,
+        Photos.ROTATION,
+    };
+
+    public static String[] PROJECTION_ACCOUNTS = {
+        Accounts._ID,
+        Accounts.ACCOUNT_NAME,
+    };
+
+    private static String SELECTION_ALBUM_PARENT_ID = Albums.PARENT_ID + " = ?";
+    private static String SELECTION_PHOTO_ALBUM_ID = Photos.ALBUM_ID + " = ?";
+    private static String SELECTION_ACCOUNT_ID = Accounts.ACCOUNT_NAME + " = ?";
+
+    public static long queryAlbumIdFromParentId(SQLiteDatabase db, long parentId) {
+        return queryId(db, Albums.TABLE, PROJECTION_ALBUMS, SELECTION_ALBUM_PARENT_ID, parentId);
+    }
+
+    public static long queryPhotoIdFromAlbumId(SQLiteDatabase db, long albumId) {
+        return queryId(db, Photos.TABLE, PROJECTION_PHOTOS, SELECTION_PHOTO_ALBUM_ID, albumId);
+    }
+
+    public static long queryAccountIdFromName(SQLiteDatabase db, String accountName) {
+        return queryId(db, Accounts.TABLE, PROJECTION_ACCOUNTS, SELECTION_ACCOUNT_ID, accountName);
+    }
+
+    public static long queryId(SQLiteDatabase db, String table, String[] projection,
+            String selection, Object parameter) {
+        String paramString = parameter == null ? null : parameter.toString();
+        String[] selectionArgs = {
+            paramString,
+        };
+        Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null, null);
+        try {
+            if (cursor.getCount() != 1 || !cursor.moveToNext()) {
+                throw new AssertionFailedError("Couldn't find item in table");
+            }
+            long id = cursor.getLong(0);
+            return id;
+        } finally {
+            cursor.close();
+        }
+    }
+
+    public static boolean insertPhoto(SQLiteDatabase db, Integer width, Integer height,
+            Long dateTaken, Long albumId, String mimeType, Long accountId) {
+        ContentValues values = new ContentValues();
+        values.put(Photos.WIDTH, width);
+        values.put(Photos.HEIGHT, height);
+        values.put(Photos.DATE_TAKEN, dateTaken);
+        values.put(Photos.ALBUM_ID, albumId);
+        values.put(Photos.MIME_TYPE, mimeType);
+        values.put(Photos.ACCOUNT_ID, accountId);
+        return db.insert(Photos.TABLE, null, values) != -1;
+    }
+
+    public static boolean insertAlbum(SQLiteDatabase db, Long parentId, String title,
+            Integer privacy, Long accountId) {
+        ContentValues values = new ContentValues();
+        values.put(Albums.PARENT_ID, parentId);
+        values.put(Albums.TITLE, title);
+        values.put(Albums.VISIBILITY, privacy);
+        values.put(Albums.ACCOUNT_ID, accountId);
+        return db.insert(Albums.TABLE, null, values) != -1;
+    }
+
+    public static boolean insertMetadata(SQLiteDatabase db, Long photosId, String key, String value) {
+        ContentValues values = new ContentValues();
+        values.put(Metadata.PHOTO_ID, photosId);
+        values.put(Metadata.KEY, key);
+        values.put(Metadata.VALUE, value);
+        return db.insert(Metadata.TABLE, null, values) != -1;
+    }
+
+    public static boolean insertAccount(SQLiteDatabase db, String name) {
+        ContentValues values = new ContentValues();
+        values.put(Accounts.ACCOUNT_NAME, name);
+        return db.insert(Accounts.TABLE, null, values) != -1;
+    }
+}
diff --git a/tests/src/com/android/photos/data/PhotoProviderTest.java b/tests/src/com/android/photos/data/PhotoProviderTest.java
new file mode 100644
index 0000000..685946e
--- /dev/null
+++ b/tests/src/com/android/photos/data/PhotoProviderTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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.photos.data;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.BaseColumns;
+import android.test.ProviderTestCase2;
+
+import com.android.photos.data.PhotoProvider.Accounts;
+import com.android.photos.data.PhotoProvider.Albums;
+import com.android.photos.data.PhotoProvider.Metadata;
+import com.android.photos.data.PhotoProvider.Photos;
+
+import java.util.ArrayList;
+
+public class PhotoProviderTest extends ProviderTestCase2<PhotoProvider> {
+    @SuppressWarnings("unused")
+    private static final String TAG = PhotoProviderTest.class.getSimpleName();
+
+    private static final String MIME_TYPE = "test/test";
+    private static final String ALBUM_TITLE = "My Album";
+    private static final long ALBUM_PARENT_ID = 100;
+    private static final String META_KEY = "mykey";
+    private static final String META_VALUE = "myvalue";
+    private static final String ACCOUNT_NAME = "foo@bar.com";
+
+    private static final Uri NO_TABLE_URI = PhotoProvider.BASE_CONTENT_URI;
+    private static final Uri BAD_TABLE_URI = Uri.withAppendedPath(PhotoProvider.BASE_CONTENT_URI,
+            "bad_table");
+
+    private static final String WHERE_METADATA_PHOTOS_ID = Metadata.PHOTO_ID + " = ?";
+    private static final String WHERE_METADATA = Metadata.PHOTO_ID + " = ? AND " + Metadata.KEY
+            + " = ?";
+
+    private long mAlbumId;
+    private long mPhotoId;
+    private long mMetadataId;
+    private long mAccountId;
+
+    private SQLiteOpenHelper mDBHelper;
+    private ContentResolver mResolver;
+    private NotificationWatcher mNotifications = new NotificationWatcher();
+
+    public PhotoProviderTest() {
+        super(PhotoProvider.class, PhotoProvider.AUTHORITY);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getMockContentResolver();
+        PhotoProvider provider = (PhotoProvider) getProvider();
+        provider.setMockNotification(mNotifications);
+        mDBHelper = provider.getDatabaseHelper();
+        SQLiteDatabase db = mDBHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            PhotoDatabaseUtils.insertAccount(db, ACCOUNT_NAME);
+            mAccountId = PhotoDatabaseUtils.queryAccountIdFromName(db, ACCOUNT_NAME);
+            PhotoDatabaseUtils.insertAlbum(db, ALBUM_PARENT_ID, ALBUM_TITLE,
+                    Albums.VISIBILITY_PRIVATE, mAccountId);
+            mAlbumId = PhotoDatabaseUtils.queryAlbumIdFromParentId(db, ALBUM_PARENT_ID);
+            PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), mAlbumId,
+                    MIME_TYPE, mAccountId);
+            mPhotoId = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, mAlbumId);
+            PhotoDatabaseUtils.insertMetadata(db, mPhotoId, META_KEY, META_VALUE);
+            String[] projection = {
+                    BaseColumns._ID,
+            };
+            Cursor cursor = db.query(Metadata.TABLE, projection, null, null, null, null, null);
+            cursor.moveToNext();
+            mMetadataId = cursor.getLong(0);
+            cursor.close();
+            db.setTransactionSuccessful();
+            mNotifications.reset();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDBHelper.close();
+        mDBHelper = null;
+        super.tearDown();
+        getMockContext().deleteDatabase(PhotoProvider.DB_NAME);
+    }
+
+    public void testDelete() {
+        try {
+            mResolver.delete(NO_TABLE_URI, null, null);
+            fail("Exeption should be thrown when no table given");
+        } catch (Exception e) {
+            // expected exception
+        }
+        try {
+            mResolver.delete(BAD_TABLE_URI, null, null);
+            fail("Exeption should be thrown when deleting from a table that doesn't exist");
+        } catch (Exception e) {
+            // expected exception
+        }
+
+        String[] selectionArgs = {
+            String.valueOf(mPhotoId)
+        };
+        // Delete some metadata
+        assertEquals(1,
+                mResolver.delete(Metadata.CONTENT_URI, WHERE_METADATA_PHOTOS_ID, selectionArgs));
+        Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+        assertEquals(1, mResolver.delete(photoUri, null, null));
+        Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+        assertEquals(1, mResolver.delete(albumUri, null, null));
+        // now delete something that isn't there
+        assertEquals(0, mResolver.delete(photoUri, null, null));
+    }
+
+    public void testDeleteMetadataId() {
+        Uri metadataUri = ContentUris.withAppendedId(Metadata.CONTENT_URI, mMetadataId);
+        assertEquals(1, mResolver.delete(metadataUri, null, null));
+        Cursor cursor = mResolver.query(Metadata.CONTENT_URI, null, null, null, null);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+    }
+
+    // Delete the album and ensure that the photos referring to the album are
+    // deleted.
+    public void testDeleteAlbumCascade() {
+        Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+        mResolver.delete(albumUri, null, null);
+        assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+        assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+        assertTrue(mNotifications.isNotified(albumUri));
+        assertEquals(3, mNotifications.notificationCount());
+        Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+                null, null, null);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+    }
+
+    // Delete all albums and ensure that photos in any album are deleted.
+    public void testDeleteAlbumCascade2() {
+        mResolver.delete(Albums.CONTENT_URI, null, null);
+        assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+        assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+        assertTrue(mNotifications.isNotified(Albums.CONTENT_URI));
+        assertEquals(3, mNotifications.notificationCount());
+        Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+                null, null, null);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+    }
+
+    // Delete a photo and ensure that the metadata for that photo are deleted.
+    public void testDeletePhotoCascade() {
+        Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+        mResolver.delete(photoUri, null, null);
+        assertTrue(mNotifications.isNotified(photoUri));
+        assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+        assertEquals(2, mNotifications.notificationCount());
+        Cursor cursor = mResolver.query(Metadata.CONTENT_URI,
+                PhotoDatabaseUtils.PROJECTION_METADATA, null, null, null);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+    }
+
+    public void testDeleteAccountCascade() {
+        Uri accountUri = ContentUris.withAppendedId(Accounts.CONTENT_URI, mAccountId);
+        SQLiteDatabase db = mDBHelper.getWritableDatabase();
+        db.beginTransaction();
+        PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null,
+                "image/jpeg", mAccountId);
+        PhotoDatabaseUtils.insertPhoto(db, 100, 100, System.currentTimeMillis(), null,
+                "image/jpeg", 0L);
+        PhotoDatabaseUtils.insertAlbum(db, null, "title", Albums.VISIBILITY_PRIVATE, 10630L);
+        db.setTransactionSuccessful();
+        db.endTransaction();
+        // ensure all pictures are there:
+        Cursor cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null);
+        assertEquals(3, cursor.getCount());
+        cursor.close();
+        // delete the account
+        assertEquals(1, mResolver.delete(accountUri, null, null));
+        // now ensure that all associated photos were deleted
+        cursor = mResolver.query(Photos.CONTENT_URI, null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        cursor.close();
+        // now ensure all associated albums were deleted.
+        cursor = mResolver.query(Albums.CONTENT_URI, null, null, null, null);
+        assertEquals(1, cursor.getCount());
+        cursor.close();
+    }
+
+    public void testGetType() {
+        // We don't return types for albums
+        assertNull(mResolver.getType(Albums.CONTENT_URI));
+
+        Uri noImage = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+        assertNull(mResolver.getType(noImage));
+
+        Uri image = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+        assertEquals(MIME_TYPE, mResolver.getType(image));
+    }
+
+    public void testInsert() {
+        ContentValues values = new ContentValues();
+        values.put(Albums.TITLE, "add me");
+        values.put(Albums.VISIBILITY, Albums.VISIBILITY_PRIVATE);
+        values.put(Albums.ACCOUNT_ID, 100L);
+        values.put(Albums.DATE_MODIFIED, 100L);
+        values.put(Albums.DATE_PUBLISHED, 100L);
+        values.put(Albums.LOCATION_STRING, "Home");
+        values.put(Albums.TITLE, "hello world");
+        values.putNull(Albums.PARENT_ID);
+        values.put(Albums.SUMMARY, "Nothing much to say about this");
+        Uri insertedUri = mResolver.insert(Albums.CONTENT_URI, values);
+        assertNotNull(insertedUri);
+        Cursor cursor = mResolver.query(insertedUri, PhotoDatabaseUtils.PROJECTION_ALBUMS, null,
+                null, null);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        cursor.close();
+    }
+
+    public void testUpdate() {
+        ContentValues values = new ContentValues();
+        // Normal update -- use an album.
+        values.put(Albums.TITLE, "foo");
+        Uri albumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId);
+        assertEquals(1, mResolver.update(albumUri, values, null, null));
+        String[] projection = {
+            Albums.TITLE,
+        };
+        Cursor cursor = mResolver.query(albumUri, projection, null, null, null);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToNext());
+        assertEquals("foo", cursor.getString(0));
+        cursor.close();
+
+        // Update a row that doesn't exist.
+        Uri noAlbumUri = ContentUris.withAppendedId(Albums.CONTENT_URI, mAlbumId + 1);
+        values.put(Albums.TITLE, "bar");
+        assertEquals(0, mResolver.update(noAlbumUri, values, null, null));
+
+        // Update a metadata value that exists.
+        ContentValues metadata = new ContentValues();
+        metadata.put(Metadata.PHOTO_ID, mPhotoId);
+        metadata.put(Metadata.KEY, META_KEY);
+        metadata.put(Metadata.VALUE, "new value");
+        assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+        projection = new String[] {
+            Metadata.VALUE,
+        };
+
+        String[] selectionArgs = {
+                String.valueOf(mPhotoId), META_KEY,
+        };
+
+        cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+                null);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToNext());
+        assertEquals("new value", cursor.getString(0));
+        cursor.close();
+
+        // Update a metadata value that doesn't exist.
+        metadata.put(Metadata.KEY, "other stuff");
+        assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+
+        selectionArgs[1] = "other stuff";
+        cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+                null);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToNext());
+        assertEquals("new value", cursor.getString(0));
+        cursor.close();
+
+        // Remove a metadata value using update.
+        metadata.putNull(Metadata.VALUE);
+        assertEquals(1, mResolver.update(Metadata.CONTENT_URI, metadata, null, null));
+        cursor = mResolver.query(Metadata.CONTENT_URI, projection, WHERE_METADATA, selectionArgs,
+                null);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+    }
+
+    public void testQuery() {
+        // Query a photo that exists.
+        Cursor cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+                null, null, null);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToNext());
+        assertEquals(mPhotoId, cursor.getLong(0));
+        cursor.close();
+
+        // Query a photo that doesn't exist.
+        Uri noPhotoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId + 1);
+        cursor = mResolver.query(noPhotoUri, PhotoDatabaseUtils.PROJECTION_PHOTOS, null, null,
+                null);
+        assertNotNull(cursor);
+        assertEquals(0, cursor.getCount());
+        cursor.close();
+
+        // Query a photo that exists using selection arguments.
+        String[] selectionArgs = {
+            String.valueOf(mPhotoId),
+        };
+
+        cursor = mResolver.query(Photos.CONTENT_URI, PhotoDatabaseUtils.PROJECTION_PHOTOS,
+                Photos._ID + " = ?", selectionArgs, null);
+        assertNotNull(cursor);
+        assertEquals(1, cursor.getCount());
+        assertTrue(cursor.moveToNext());
+        assertEquals(mPhotoId, cursor.getLong(0));
+        cursor.close();
+    }
+
+    public void testUpdatePhotoNotification() {
+        Uri photoUri = ContentUris.withAppendedId(Photos.CONTENT_URI, mPhotoId);
+        ContentValues values = new ContentValues();
+        values.put(Photos.MIME_TYPE, "not-a/mime-type");
+        mResolver.update(photoUri, values, null, null);
+        assertTrue(mNotifications.isNotified(photoUri));
+    }
+
+    public void testUpdateMetadataNotification() {
+        ContentValues values = new ContentValues();
+        values.put(Metadata.PHOTO_ID, mPhotoId);
+        values.put(Metadata.KEY, META_KEY);
+        values.put(Metadata.VALUE, "hello world");
+        mResolver.update(Metadata.CONTENT_URI, values, null, null);
+        assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+    }
+
+    public void testBatchTransaction() throws RemoteException, OperationApplicationException {
+        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+        ContentProviderOperation.Builder insert = ContentProviderOperation
+                .newInsert(Photos.CONTENT_URI);
+        insert.withValue(Photos.WIDTH, 200L);
+        insert.withValue(Photos.HEIGHT, 100L);
+        insert.withValue(Photos.DATE_TAKEN, System.currentTimeMillis());
+        insert.withValue(Photos.ALBUM_ID, 1000L);
+        insert.withValue(Photos.MIME_TYPE, "image/jpg");
+        insert.withValue(Photos.ACCOUNT_ID, 1L);
+        operations.add(insert.build());
+        ContentProviderOperation.Builder update = ContentProviderOperation.newUpdate(Photos.CONTENT_URI);
+        update.withValue(Photos.DATE_MODIFIED, System.currentTimeMillis());
+        String[] whereArgs = {
+            "100",
+        };
+        String where = Photos.WIDTH + " = ?";
+        update.withSelection(where, whereArgs);
+        operations.add(update.build());
+        ContentProviderOperation.Builder delete = ContentProviderOperation
+                .newDelete(Photos.CONTENT_URI);
+        delete.withSelection(where, whereArgs);
+        operations.add(delete.build());
+        mResolver.applyBatch(PhotoProvider.AUTHORITY, operations);
+        assertEquals(3, mNotifications.notificationCount());
+        SQLiteDatabase db = mDBHelper.getReadableDatabase();
+        long id = PhotoDatabaseUtils.queryPhotoIdFromAlbumId(db, 1000L);
+        Uri uri = ContentUris.withAppendedId(Photos.CONTENT_URI, id);
+        assertTrue(mNotifications.isNotified(uri));
+        assertTrue(mNotifications.isNotified(Metadata.CONTENT_URI));
+        assertTrue(mNotifications.isNotified(Photos.CONTENT_URI));
+    }
+
+}
diff --git a/tests/src/com/android/photos/data/TestHelper.java b/tests/src/com/android/photos/data/TestHelper.java
new file mode 100644
index 0000000..338e160
--- /dev/null
+++ b/tests/src/com/android/photos/data/TestHelper.java
@@ -0,0 +1,53 @@
+/*
+ * 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.photos.data;
+
+import android.util.Log;
+
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.lang.reflect.Method;
+
+public class TestHelper {
+    private static String TAG = TestHelper.class.getSimpleName();
+
+    public interface TestInitialization {
+        void initialize(TestCase testCase);
+    }
+
+    public static void addTests(Class<? extends TestCase> testClass, TestSuite suite,
+            TestInitialization initialization) {
+        for (Method method : testClass.getDeclaredMethods()) {
+            if (method.getName().startsWith("test") && method.getParameterTypes().length == 0) {
+                TestCase test;
+                try {
+                    test = testClass.newInstance();
+                    test.setName(method.getName());
+                    initialization.initialize(test);
+                    suite.addTest(test);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Failed to create test case", e);
+                } catch (InstantiationException e) {
+                    Log.e(TAG, "Failed to create test case", e);
+                } catch (IllegalAccessException e) {
+                    Log.e(TAG, "Failed to create test case", e);
+                }
+            }
+        }
+    }
+
+}
diff --git a/tests_camera/Android.mk b/tests_camera/Android.mk
new file mode 100644
index 0000000..81ea50d
--- /dev/null
+++ b/tests_camera/Android.mk
@@ -0,0 +1,18 @@
+ifeq (0,1)
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SDK_VERSION := 16
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CameraTests
+
+LOCAL_INSTRUMENTATION_FOR := Gallery2
+
+include $(BUILD_PACKAGE)
+endif
diff --git a/tests_camera/AndroidManifest.xml b/tests_camera/AndroidManifest.xml
new file mode 100644
index 0000000..164bbd5
--- /dev/null
+++ b/tests_camera/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.camera.tests">
+
+    <uses-permission android:name="android.permission.INJECT_EVENTS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="com.android.camera.CameraLaunchPerformance"
+            android:targetPackage="com.android.camera"
+            android:label="Camera Launch Performance">
+    </instrumentation>
+
+    <instrumentation android:name="com.android.camera.stress.CameraStressTestRunner"
+            android:targetPackage="com.android.camera"
+            android:label="Camera stress test runner">
+    </instrumentation>
+
+    <instrumentation android:name="com.android.camera.CameraTestRunner"
+            android:targetPackage="com.android.camera"
+            android:label="Camera continuous test runner">
+    </instrumentation>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+             android:targetPackage="com.android.camera"
+             android:label="Tests for Camera application."/>
+</manifest>
diff --git a/tests_camera/src/com/android/camera/CameraLaunchPerformance.java b/tests_camera/src/com/android/camera/CameraLaunchPerformance.java
new file mode 100644
index 0000000..fe2b776
--- /dev/null
+++ b/tests_camera/src/com/android/camera/CameraLaunchPerformance.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2007 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.app.Activity;
+import android.os.Bundle;
+import android.test.LaunchPerformanceBase;
+
+/**
+ * Instrumentation class for Camera launch performance testing.
+ */
+public class CameraLaunchPerformance extends LaunchPerformanceBase {
+    @SuppressWarnings("unused")
+    private static final String TAG = "CameraLaunchPerformance";
+
+    @Override
+    public void onCreate(Bundle arguments) {
+        super.onCreate(arguments);
+        mIntent.setClassName(getTargetContext(),
+                "com.android.camera.CameraActivity");
+        start();
+    }
+
+    /**
+     * Calls LaunchApp and finish.
+     */
+    @Override
+    public void onStart() {
+        super.onStart();
+        LaunchApp();
+        finish(Activity.RESULT_OK, mResults);
+    }
+}
diff --git a/tests_camera/src/com/android/camera/CameraTestRunner.java b/tests_camera/src/com/android/camera/CameraTestRunner.java
new file mode 100755
index 0000000..96c48a4
--- /dev/null
+++ b/tests_camera/src/com/android/camera/CameraTestRunner.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2012 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.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+
+import com.android.camera.activity.CameraActivityTest;
+import com.android.camera.functional.CameraTest;
+import com.android.camera.functional.ImageCaptureIntentTest;
+import com.android.camera.functional.VideoCaptureIntentTest;
+import com.android.camera.unittest.CameraUnitTest;
+
+import junit.framework.TestSuite;
+
+
+public class CameraTestRunner extends InstrumentationTestRunner {
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(CameraActivityTest.class);
+        suite.addTestSuite(CameraTest.class);
+        suite.addTestSuite(ImageCaptureIntentTest.class);
+        suite.addTestSuite(VideoCaptureIntentTest.class);
+        suite.addTestSuite(CameraUnitTest.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return CameraTestRunner.class.getClassLoader();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/StressTests.java b/tests_camera/src/com/android/camera/StressTests.java
new file mode 100755
index 0000000..7ed8317
--- /dev/null
+++ b/tests_camera/src/com/android/camera/StressTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import com.android.camera.stress.ImageCapture;
+import com.android.camera.stress.SwitchPreview;
+import com.android.camera.stress.CameraLatency;
+import com.android.camera.stress.CameraStartUp;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+
+/**
+ * Instrumentation Test Runner for all Camera tests.
+ *
+ * Running all tests:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.StressTests \
+ *    -w com.android.camera.tests/com.android.camera.stress.CameraStressTestRunner
+ */
+
+public class StressTests extends TestSuite {
+    public static Test suite() {
+        TestSuite result = new TestSuite();
+        result.addTestSuite(SwitchPreview.class);
+        result.addTestSuite(ImageCapture.class);
+        result.addTestSuite(CameraLatency.class);
+        result.addTestSuite(CameraStartUp.class);
+        return result;
+    }
+}
diff --git a/tests_camera/src/com/android/camera/UnitTests.java b/tests_camera/src/com/android/camera/UnitTests.java
new file mode 100644
index 0000000..e56a907
--- /dev/null
+++ b/tests_camera/src/com/android/camera/UnitTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2008 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.test.suitebuilder.UnitTestSuiteBuilder;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * TestSuite for all Camera unit tests.
+ */
+public class UnitTests extends TestSuite {
+
+    public static Test suite() {
+        return new UnitTestSuiteBuilder(UnitTests.class)
+                .includePackages("com.android.camera.unittest")
+                .named("Camera Unit Tests")
+                .build();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/activity/CameraActivityTest.java b/tests_camera/src/com/android/camera/activity/CameraActivityTest.java
new file mode 100644
index 0000000..eb027e9
--- /dev/null
+++ b/tests_camera/src/com/android/camera/activity/CameraActivityTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 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.activity;
+
+import android.hardware.Camera.Parameters;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.CameraHolder;
+import com.android.gallery3d.R;
+
+import static com.google.testing.littlemock.LittleMock.doReturn;
+
+public class CameraActivityTest extends CameraTestCase <CameraActivity> {
+    public CameraActivityTest() {
+        super(CameraActivity.class);
+    }
+
+    @LargeTest
+    public void testFailToConnect() throws Exception {
+        super.internalTestFailToConnect();
+    }
+
+    @LargeTest
+    public void testTakePicture() throws Exception {
+        CameraHolder.injectMockCamera(mCameraInfo, mOneMockCamera);
+
+        getActivity();
+        getInstrumentation().waitForIdleSync();
+
+        // Press shutter button to take a picture.
+        performClick(R.id.shutter_button);
+        getInstrumentation().waitForIdleSync();
+
+        // Force the activity to finish.
+        getActivity().finish();
+        getInstrumentation().waitForIdleSync();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/activity/CameraTestCase.java b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
new file mode 100644
index 0000000..27be3c7
--- /dev/null
+++ b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2012 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.activity;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.hardware.Camera;
+import android.hardware.Camera.AutoFocusCallback;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.ShutterCallback;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.camera.CameraHolder;
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+import static com.google.testing.littlemock.LittleMock.mock;
+import static com.google.testing.littlemock.LittleMock.doAnswer;
+import static com.google.testing.littlemock.LittleMock.doReturn;
+import static com.google.testing.littlemock.LittleMock.anyObject;
+import com.google.testing.littlemock.AppDataDirGuesser;
+import com.google.testing.littlemock.ArgumentCaptor;
+import com.google.testing.littlemock.Captor;
+import com.google.testing.littlemock.LittleMock;
+import com.google.testing.littlemock.Mock;
+
+import java.io.File;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+
+public class CameraTestCase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
+    protected CameraInfo mCameraInfo[];
+    protected CameraProxy mMockCamera[];
+    protected CameraInfo mOneCameraInfo[];
+    protected CameraProxy mOneMockCamera[];
+    private static Parameters mParameters;
+    private byte[] mBlankJpeg;
+    @Mock private CameraProxy mMockBackCamera;
+    @Mock private CameraProxy mMockFrontCamera;
+    @Captor private ArgumentCaptor<ShutterCallback> mShutterCallback;
+    @Captor private ArgumentCaptor<PictureCallback> mRawPictureCallback;
+    @Captor private ArgumentCaptor<PictureCallback> mJpegPictureCallback;
+    @Captor private ArgumentCaptor<AutoFocusCallback> mAutoFocusCallback;
+    Callable<Object> mAutoFocusCallable = new AutoFocusCallable();
+    Callable<Object> mTakePictureCallable = new TakePictureCallable();
+
+    private class TakePictureCallable implements Callable<Object> {
+        @Override
+        public Object call() throws Exception {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    readBlankJpeg();
+                    Camera camera = mOneMockCamera[0].getCamera();
+                    mShutterCallback.getValue().onShutter();
+                    mRawPictureCallback.getValue().onPictureTaken(null, camera);
+                    mJpegPictureCallback.getValue().onPictureTaken(mBlankJpeg, camera);
+                }
+            };
+            // Probably need some delay. Make sure shutter callback is called
+            // after onShutterButtonFocus(false).
+            getActivity().findViewById(R.id.gl_root_view).postDelayed(runnable, 50);
+            return null;
+        }
+   }
+
+    private class AutoFocusCallable implements Callable<Object> {
+        @Override
+        public Object call() throws Exception {
+            Runnable runnable = new Runnable() {
+                @Override
+                public void run() {
+                    Camera camera = mOneMockCamera[0].getCamera();
+                    mAutoFocusCallback.getValue().onAutoFocus(true, camera);
+                }
+            };
+            // Need some delay. Otherwise, focus callback will be run before
+            // onShutterButtonClick
+            getActivity().findViewById(R.id.gl_root_view).postDelayed(runnable, 50);
+            return null;
+        }
+   }
+
+    public CameraTestCase(Class<T> activityClass) {
+        super(activityClass);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        AppDataDirGuesser.setInstance(new AppDataDirGuesser() {
+            @Override
+            public File guessSuitableDirectoryForGeneratedClasses() {
+                return getInstrumentation().getTargetContext().getCacheDir();
+            }
+        });
+        AppDataDirGuesser.getsInstance().guessSuitableDirectoryForGeneratedClasses();
+        LittleMock.initMocks(this);
+        mCameraInfo = new CameraInfo[2];
+        mCameraInfo[0] = new CameraInfo();
+        mCameraInfo[0].facing = CameraInfo.CAMERA_FACING_BACK;
+        mCameraInfo[1] = new CameraInfo();
+        mCameraInfo[1].facing = CameraInfo.CAMERA_FACING_FRONT;
+        mMockCamera = new CameraProxy[2];
+        mMockCamera[0] = mMockBackCamera;
+        mMockCamera[1] = mMockFrontCamera;
+        doReturn(getParameters()).when(mMockCamera[0]).getParameters();
+        doReturn(getParameters()).when(mMockCamera[1]).getParameters();
+
+        mOneCameraInfo = new CameraInfo[1];
+        mOneCameraInfo[0] = new CameraInfo();
+        mOneCameraInfo[0].facing = CameraInfo.CAMERA_FACING_BACK;
+        mOneMockCamera = new CameraProxy[1];
+        mOneMockCamera[0] = mMockBackCamera;
+        doReturn(getParameters()).when(mOneMockCamera[0]).getParameters();
+
+        // Mock takePicture call.
+        doAnswer(mTakePictureCallable).when(mMockBackCamera).takePicture(
+                mShutterCallback.capture(), mRawPictureCallback.capture(),
+                (PictureCallback) anyObject(), mJpegPictureCallback.capture());
+
+        // Mock autoFocus call.
+        doAnswer(mAutoFocusCallable).when(mMockBackCamera).autoFocus(
+                mAutoFocusCallback.capture());
+    }
+
+    private void readBlankJpeg() {
+        InputStream ins = getActivity().getResources().openRawResource(R.raw.blank);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        int size = 0;
+
+        // Read the entire resource into a local byte buffer.
+        byte[] buffer = new byte[1024];
+        try {
+            while((size = ins.read(buffer, 0, 1024)) >= 0){
+                outputStream.write(buffer, 0, size);
+            }
+        } catch (IOException e) {
+            // ignore
+        } finally {
+            Util.closeSilently(ins);
+        }
+        mBlankJpeg = outputStream.toByteArray();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        CameraHolder.injectMockCamera(null,  null);
+    }
+
+    protected void internalTestFailToConnect() throws Exception {
+        CameraHolder.injectMockCamera(mCameraInfo, null);
+
+        getActivity();
+        Instrumentation inst = getInstrumentation();
+        inst.waitForIdleSync();
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER); // close dialog
+    }
+
+    protected void performClick(final int id) {
+        Activity activity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        assertNotNull(activity.findViewById(id));
+        Instrumentation inst = getInstrumentation();
+        inst.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                View v = getActivity().findViewById(id);
+                float x = (v.getLeft() + v.getRight()) / 2;
+                float y = (v.getTop() + v.getBottom()) / 2;
+                MotionEvent down = MotionEvent.obtain(0, 0,
+                        MotionEvent.ACTION_DOWN, x, y, 0, 0, 0, 0, 0, 0, 0);
+                MotionEvent up = MotionEvent.obtain(0, 0,
+                        MotionEvent.ACTION_UP, x, y, 0, 0, 0, 0, 0, 0, 0);
+                View parent = (View) v.getParent();
+                parent.dispatchTouchEvent(down);
+                parent.dispatchTouchEvent(up);
+            }
+        });
+        inst.waitForIdleSync();
+    }
+
+    protected void assertViewNotExist(int id) {
+        Activity activity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        assertNull(activity.findViewById(id));
+    }
+
+    protected void assertViewNotVisible(int id) {
+        Activity activity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        View view = activity.findViewById(id);
+        assertTrue(view.getVisibility() != View.VISIBLE);
+    }
+
+    protected static Parameters getParameters() {
+        synchronized (CameraTestCase.class) {
+            if (mParameters == null) {
+                mParameters = android.hardware.Camera.getEmptyParameters();
+                mParameters.unflatten("preview-format-values=yuv420sp,yuv420p,yuv422i-yuyv,yuv420p;" +
+                        "preview-format=yuv420sp;" +
+                        "preview-size-values=800x480;preview-size=800x480;" +
+                        "picture-size-values=320x240;picture-size=320x240;" +
+                        "jpeg-thumbnail-size-values=320x240,0x0;jpeg-thumbnail-width=320;jpeg-thumbnail-height=240;" +
+                        "jpeg-thumbnail-quality=60;jpeg-quality=95;" +
+                        "preview-frame-rate-values=30,15;preview-frame-rate=30;" +
+                        "focus-mode-values=continuous-video,auto,macro,infinity,continuous-picture;focus-mode=auto;" +
+                        "preview-fps-range-values=(15000,30000);preview-fps-range=15000,30000;" +
+                        "scene-mode-values=auto,action,night;scene-mode=auto;" +
+                        "flash-mode-values=off,on,auto,torch;flash-mode=off;" +
+                        "whitebalance-values=auto,daylight,fluorescent,incandescent;whitebalance=auto;" +
+                        "effect-values=none,mono,sepia;effect=none;" +
+                        "zoom-supported=true;zoom-ratios=100,200,400;max-zoom=2;" +
+                        "picture-format-values=jpeg;picture-format=jpeg;" +
+                        "min-exposure-compensation=-30;max-exposure-compensation=30;" +
+                        "exposure-compensation=0;exposure-compensation-step=0.1;" +
+                        "horizontal-view-angle=40;vertical-view-angle=40;");
+            }
+        }
+        return mParameters;
+    }
+}
diff --git a/tests_camera/src/com/android/camera/functional/CameraTest.java b/tests_camera/src/com/android/camera/functional/CameraTest.java
new file mode 100644
index 0000000..3fdebc0
--- /dev/null
+++ b/tests_camera/src/com/android/camera/functional/CameraTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 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.functional;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Process;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+public class CameraTest extends InstrumentationTestCase {
+    @LargeTest
+    public void testVideoCaptureIntentFdLeak() throws Exception {
+        Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.parse("file://"
+                + Environment.getExternalStorageDirectory().toString()
+                + "test_fd_leak.3gp"));
+        getInstrumentation().startActivitySync(intent).finish();
+        // Test if the fd is closed.
+        for (File f: new File("/proc/" + Process.myPid() + "/fd").listFiles()) {
+            assertEquals(-1, f.getCanonicalPath().indexOf("test_fd_leak.3gp"));
+        }
+    }
+
+    @LargeTest
+    public void testActivityLeak() throws Exception {
+        checkActivityLeak(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
+        checkActivityLeak(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+    }
+
+    private void checkActivityLeak(String action) throws Exception {
+        final int TEST_COUNT = 5;
+        Intent intent = new Intent(action);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClass(getInstrumentation().getTargetContext(),
+                CameraActivity.class);
+        ArrayList<WeakReference<Activity>> refs =
+                new ArrayList<WeakReference<Activity>>();
+        for (int i = 0; i < TEST_COUNT; i++) {
+            Activity activity = getInstrumentation().startActivitySync(intent);
+            refs.add(new WeakReference<Activity>(activity));
+            activity.finish();
+            getInstrumentation().waitForIdleSync();
+            activity = null;
+        }
+        Runtime.getRuntime().gc();
+        Runtime.getRuntime().runFinalization();
+        Runtime.getRuntime().gc();
+        int refCount = 0;
+        for (WeakReference<Activity> c: refs) {
+            if (c.get() != null) refCount++;
+        }
+        // If applications are leaking activity, every reference is reachable.
+        assertTrue(refCount != TEST_COUNT);
+    }
+}
diff --git a/tests_camera/src/com/android/camera/functional/ImageCaptureIntentTest.java b/tests_camera/src/com/android/camera/functional/ImageCaptureIntentTest.java
new file mode 100644
index 0000000..54ac1b4
--- /dev/null
+++ b/tests_camera/src/com/android/camera/functional/ImageCaptureIntentTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2011 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.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+
+public class ImageCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private Intent mIntent;
+
+    public ImageCaptureIntentTest() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+    }
+
+    @LargeTest
+    public void testNoExtraOutput() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        takePicture();
+        pressDone();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+        Intent resultData = getActivity().getResultData();
+        Bitmap bitmap = (Bitmap) resultData.getParcelableExtra("data");
+        assertNotNull(bitmap);
+        assertTrue(bitmap.getWidth() > 0);
+        assertTrue(bitmap.getHeight() > 0);
+    }
+
+    @LargeTest
+    public void testExtraOutput() throws Exception {
+        File file = new File(Environment.getExternalStorageDirectory(),
+            "test.jpg");
+        BufferedInputStream stream = null;
+        byte[] jpegData;
+
+        try {
+            mIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
+            setActivityIntent(mIntent);
+            getActivity();
+
+            takePicture();
+            pressDone();
+
+            assertTrue(getActivity().isFinishing());
+            assertEquals(Activity.RESULT_OK, getActivity().getResultCode());
+
+            // Verify the jpeg file
+            int fileLength = (int) file.length();
+            assertTrue(fileLength > 0);
+            jpegData = new byte[fileLength];
+            stream = new BufferedInputStream(new FileInputStream(file));
+            stream.read(jpegData);
+        } finally {
+            if (stream != null) stream.close();
+            file.delete();
+        }
+
+        Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+        assertTrue(b.getWidth() > 0);
+        assertTrue(b.getHeight() > 0);
+    }
+
+    @LargeTest
+    public void testCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testSnapshotCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        takePicture();
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    private void takePicture() throws Exception {
+        getInstrumentation().sendKeySync(
+                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS));
+        getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+        Thread.sleep(4000);
+    }
+
+    private void pressDone() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_done).performClick();
+            }
+        });
+    }
+
+    private void pressCancel() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_cancel).performClick();
+            }
+        });
+    }
+}
diff --git a/tests_camera/src/com/android/camera/functional/VideoCaptureIntentTest.java b/tests_camera/src/com/android/camera/functional/VideoCaptureIntentTest.java
new file mode 100644
index 0000000..43e91ca
--- /dev/null
+++ b/tests_camera/src/com/android/camera/functional/VideoCaptureIntentTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2011 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.functional;
+
+import com.android.camera.CameraActivity;
+import com.android.gallery3d.R;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.File;
+
+public class VideoCaptureIntentTest extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private static final String TAG = "VideoCaptureIntentTest";
+    private Intent mIntent;
+    private Uri mVideoUri;
+    private File mFile, mFile2;
+
+    public VideoCaptureIntentTest() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mVideoUri != null) {
+            ContentResolver resolver = getActivity().getContentResolver();
+            Uri query = mVideoUri.buildUpon().build();
+            String[] projection = new String[] {VideoColumns.DATA};
+
+            Cursor cursor = null;
+            try {
+                cursor = resolver.query(query, projection, null, null, null);
+                if (cursor != null && cursor.moveToFirst()) {
+                    new File(cursor.getString(0)).delete();
+                }
+            } finally {
+                if (cursor != null) cursor.close();
+            }
+
+            resolver.delete(mVideoUri, null, null);
+        }
+        if (mFile != null) mFile.delete();
+        if (mFile2 != null) mFile2.delete();
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void testNoExtraOutput() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        Intent resultData = getActivity().getResultData();
+        mVideoUri = resultData.getData();
+        assertNotNull(mVideoUri);
+        verify(getActivity(), mVideoUri);
+    }
+
+    @LargeTest
+    public void testExtraOutput() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+    }
+
+    @LargeTest
+    public void testCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testRecordCancel() throws Exception {
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressCancel();
+
+        assertTrue(getActivity().isFinishing());
+        assertEquals(Activity.RESULT_CANCELED, getActivity().getResultCode());
+    }
+
+    @LargeTest
+    public void testExtraSizeLimit() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        final long sizeLimit = 500000;  // bytes
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_SIZE_LIMIT, sizeLimit);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);  // use low quality to speed up
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo(5000);
+        pressDone();
+
+        verify(getActivity(), uri);
+        long length = mFile.length();
+        Log.v(TAG, "Video size is " + length + " bytes.");
+        assertTrue(length > 0);
+        assertTrue("Actual size=" + length, length <= sizeLimit);
+    }
+
+    @LargeTest
+    public void testExtraDurationLimit() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        final int durationLimit = 2;  // seconds
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, durationLimit);
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo(5000);
+        pressDone();
+
+        int duration = verify(getActivity(), uri);
+        // The duraion should be close to to the limit. The last video duration
+        // also has duration, so the total duration may exceeds the limit a
+        // little bit.
+        Log.v(TAG, "Video length is " + duration + " ms.");
+        assertTrue(duration  < (durationLimit + 1) * 1000);
+    }
+
+    @LargeTest
+    public void testExtraVideoQuality() throws Exception {
+        mFile = new File(Environment.getExternalStorageDirectory(), "video.tmp");
+        mFile2 = new File(Environment.getExternalStorageDirectory(), "video2.tmp");
+
+        Uri uri = Uri.fromFile(mFile);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);  // low quality
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+        setActivity(null);
+
+        uri = Uri.fromFile(mFile2);
+        mIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+        mIntent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);  // high quality
+        setActivityIntent(mIntent);
+        getActivity();
+
+        recordVideo();
+        pressDone();
+
+        verify(getActivity(), uri);
+        assertTrue(mFile.length() <= mFile2.length());
+    }
+
+    // Verify result code, result data, and the duration.
+    private int verify(CameraActivity activity, Uri uri) throws Exception {
+        assertTrue(activity.isFinishing());
+        assertEquals(Activity.RESULT_OK, activity.getResultCode());
+
+        // Verify the video file
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(activity, uri);
+        String duration = retriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertNotNull(duration);
+        int durationValue = Integer.parseInt(duration);
+        Log.v(TAG, "Video duration is " + durationValue);
+        assertTrue(durationValue > 0);
+        return durationValue;
+    }
+
+    private void recordVideo(int ms) throws Exception {
+        getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+        Thread.sleep(ms);
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                // If recording is in progress, stop it. Run these atomically in
+                // UI thread.
+                CameraActivity activity = getActivity();
+                if (activity.isRecording()) {
+                    activity.findViewById(R.id.shutter_button).performClick();
+                }
+            }
+        });
+    }
+
+    private void recordVideo() throws Exception {
+        recordVideo(2000);
+    }
+
+    private void pressDone() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_done).performClick();
+            }
+        });
+    }
+
+    private void pressCancel() {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getActivity().findViewById(R.id.btn_cancel).performClick();
+            }
+        });
+    }
+}
diff --git a/tests_camera/src/com/android/camera/power/ImageAndVideoCapture.java b/tests_camera/src/com/android/camera/power/ImageAndVideoCapture.java
new file mode 100755
index 0000000..b89b764
--- /dev/null
+++ b/tests_camera/src/com/android/camera/power/ImageAndVideoCapture.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.power;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Instrumentation;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.content.Intent;
+/**
+ * Junit / Instrumentation test case for camera power measurement
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e com.android.camera.power.ImageAndVideoCapture \
+ *    -w com.android.camera.tests/android.test.InstrumentationTestRunner
+ *
+ */
+
+public class ImageAndVideoCapture extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private String TAG = "ImageAndVideoCapture";
+    private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 5;
+    private static final int TOTAL_NUMBER_OF_VIDEOCAPTURE = 5;
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1500;   //1.5 sedconds
+    private static final long WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN = 10000; //10 seconds
+    private static final long WAIT_FOR_PREVIEW = 1500; //1.5 seconds
+    private static final long WAIT_FOR_STABLE_STATE = 2000; //2 seconds
+
+    public ImageAndVideoCapture() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void testLaunchCamera() {
+        // This test case capture the baseline for the image preview.
+        try {
+            Thread.sleep(WAIT_FOR_STABLE_STATE);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            assertTrue("testImageCaptureDoNothing", false);
+        }
+    }
+
+    @LargeTest
+    public void testCapture5Image() {
+        // This test case will use the default camera setting
+        Instrumentation inst = getInstrumentation();
+        try {
+            for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+            }
+            Thread.sleep(WAIT_FOR_STABLE_STATE);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            assertTrue("testImageCapture", false);
+        }
+    }
+
+    @LargeTest
+    public void testCapture5Videos() {
+        // This test case will use the default camera setting
+        Instrumentation inst = getInstrumentation();
+        try {
+            // Switch to the video mode
+            Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+            intent.setClass(getInstrumentation().getTargetContext(),
+                    CameraActivity.class);
+            getActivity().startActivity(intent);
+            for (int i = 0; i < TOTAL_NUMBER_OF_VIDEOCAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                // record a video
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_VIDEO_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_PREVIEW);
+            }
+            Thread.sleep(WAIT_FOR_STABLE_STATE);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            assertTrue("testVideoCapture", false);
+        }
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/CameraLatency.java b/tests_camera/src/com/android/camera/stress/CameraLatency.java
new file mode 100755
index 0000000..35ff717
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/CameraLatency.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ */
+
+public class CameraLatency extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private String TAG = "CameraLatency";
+    private static final int TOTAL_NUMBER_OF_IMAGECAPTURE = 20;
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 4000;
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+
+    private long mTotalAutoFocusTime;
+    private long mTotalShutterLag;
+    private long mTotalShutterToPictureDisplayedTime;
+    private long mTotalPictureDisplayedToJpegCallbackTime;
+    private long mTotalJpegCallbackFinishTime;
+    private long mAvgAutoFocusTime;
+    private long mAvgShutterLag = mTotalShutterLag;
+    private long mAvgShutterToPictureDisplayedTime;
+    private long mAvgPictureDisplayedToJpegCallbackTime;
+    private long mAvgJpegCallbackFinishTime;
+
+    public CameraLatency() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void testImageCapture() {
+        Log.v(TAG, "start testImageCapture test");
+        Instrumentation inst = getInstrumentation();
+        inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+        try {
+            for (int i = 0; i < TOTAL_NUMBER_OF_IMAGECAPTURE; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                //skip the first measurement
+                if (i != 0) {
+                    CameraActivity c = getActivity();
+
+                    // if any of the latency var accessor methods return -1 then the
+                    // camera is set to a different module other than PhotoModule so
+                    // skip the shot and try again
+                    if (c.getAutoFocusTime() != -1) {
+                        mTotalAutoFocusTime += c.getAutoFocusTime();
+                        mTotalShutterLag += c.getShutterLag();
+                        mTotalShutterToPictureDisplayedTime +=
+                                c.getShutterToPictureDisplayedTime();
+                        mTotalPictureDisplayedToJpegCallbackTime +=
+                                c.getPictureDisplayedToJpegCallbackTime();
+                        mTotalJpegCallbackFinishTime += c.getJpegCallbackFinishTime();
+                    }
+                    else {
+                        i--;
+                        continue;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+        }
+        //ToDO: yslau
+        //1) Need to get the baseline from the cupcake so that we can add the
+        //failure condition of the camera latency.
+        //2) Only count those number with succesful capture. Set the timer to invalid
+        //before capture and ignore them if the value is invalid
+        int numberofRun = TOTAL_NUMBER_OF_IMAGECAPTURE - 1;
+        mAvgAutoFocusTime = mTotalAutoFocusTime / numberofRun;
+        mAvgShutterLag = mTotalShutterLag / numberofRun;
+        mAvgShutterToPictureDisplayedTime =
+                mTotalShutterToPictureDisplayedTime / numberofRun;
+        mAvgPictureDisplayedToJpegCallbackTime =
+                mTotalPictureDisplayedToJpegCallbackTime / numberofRun;
+        mAvgJpegCallbackFinishTime =
+                mTotalJpegCallbackFinishTime / numberofRun;
+
+        try {
+            FileWriter fstream = null;
+            fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            BufferedWriter out = new BufferedWriter(fstream);
+            out.write("Camera Latency : \n");
+            out.write("Number of loop: " + TOTAL_NUMBER_OF_IMAGECAPTURE + "\n");
+            out.write("Avg AutoFocus = " + mAvgAutoFocusTime + "\n");
+            out.write("Avg mShutterLag = " + mAvgShutterLag + "\n");
+            out.write("Avg mShutterToPictureDisplayedTime = "
+                    + mAvgShutterToPictureDisplayedTime + "\n");
+            out.write("Avg mPictureDisplayedToJpegCallbackTime = "
+                    + mAvgPictureDisplayedToJpegCallbackTime + "\n");
+            out.write("Avg mJpegCallbackFinishTime = " +
+                    mAvgJpegCallbackFinishTime + "\n");
+            out.close();
+            fstream.close();
+        } catch (Exception e) {
+            fail("Camera Latency write output to file");
+        }
+        Log.v(TAG, "The Image capture wait time = " +
+            WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+        Log.v(TAG, "Avg AutoFocus = " + mAvgAutoFocusTime);
+        Log.v(TAG, "Avg mShutterLag = " + mAvgShutterLag);
+        Log.v(TAG, "Avg mShutterToPictureDisplayedTime = "
+                + mAvgShutterToPictureDisplayedTime);
+        Log.v(TAG, "Avg mPictureDisplayedToJpegCallbackTime = "
+                + mAvgPictureDisplayedToJpegCallbackTime);
+        Log.v(TAG, "Avg mJpegCallbackFinishTime = " + mAvgJpegCallbackFinishTime);
+    }
+}
+
diff --git a/tests_camera/src/com/android/camera/stress/CameraStartUp.java b/tests_camera/src/com/android/camera/stress/CameraStartUp.java
new file mode 100644
index 0000000..94e9a94
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/CameraStartUp.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+
+/**
+ * Test cases to measure the camera and video recorder startup time.
+ */
+public class CameraStartUp extends InstrumentationTestCase {
+
+    private static final int TOTAL_NUMBER_OF_STARTUP = 20;
+
+    private String TAG = "CameraStartUp";
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private static int WAIT_TIME_FOR_PREVIEW = 1500; //1.5 second
+
+    private long launchCamera() {
+        long startupTime = 0;
+        try {
+            Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            long beforeStart = System.currentTimeMillis();
+            Instrumentation inst = getInstrumentation();
+            Activity cameraActivity = inst.startActivitySync(intent);
+            long cameraStarted = System.currentTimeMillis();
+            Thread.sleep(WAIT_TIME_FOR_PREVIEW);
+            cameraActivity.finish();
+            startupTime = cameraStarted - beforeStart;
+            Thread.sleep(1000);
+            Log.v(TAG, "camera startup time: " + startupTime);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            fail("Fails to get the output file");
+        }
+        return startupTime;
+    }
+
+    private long launchVideo() {
+        long startupTime = 0;
+
+        try {
+            Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+            intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            long beforeStart = System.currentTimeMillis();
+            Instrumentation inst = getInstrumentation();
+            Activity recorderActivity = inst.startActivitySync(intent);
+            long cameraStarted = System.currentTimeMillis();
+            recorderActivity.finish();
+            startupTime = cameraStarted - beforeStart;
+            Log.v(TAG, "Video Startup Time = " + startupTime);
+            // wait for 1s to make sure it reach a clean stage
+            Thread.sleep(WAIT_TIME_FOR_PREVIEW);
+            Log.v(TAG, "video startup time: " + startupTime);
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception", e);
+            fail("Fails to launch video output file");
+        }
+        return startupTime;
+    }
+
+    private void writeToOutputFile(long totalStartupTime,
+            String individualStartupTime, boolean firstStartUp, String Type) throws Exception {
+        // TODO (yslau) : Need to integrate the output data with central
+        // dashboard
+        try {
+            FileWriter fstream = null;
+            fstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            BufferedWriter out = new BufferedWriter(fstream);
+            if (firstStartUp) {
+                out.write("First " + Type + " Startup: " + totalStartupTime + "\n");
+            } else {
+                long averageStartupTime = totalStartupTime / (TOTAL_NUMBER_OF_STARTUP -1);
+                out.write(Type + "startup time: " + "\n");
+                out.write("Number of loop: " + (TOTAL_NUMBER_OF_STARTUP -1)  + "\n");
+                out.write(individualStartupTime + "\n\n");
+                out.write(Type + " average startup time: " + averageStartupTime + " ms\n\n");
+            }
+            out.close();
+            fstream.close();
+        } catch (Exception e) {
+            fail("Camera write output to file");
+        }
+    }
+
+    @LargeTest
+    public void testLaunchVideo() throws Exception {
+        String individualStartupTime;
+        individualStartupTime = "Individual Video Startup Time = ";
+        long totalStartupTime = 0;
+        long startupTime = 0;
+        for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) {
+            if (i == 0) {
+                // Capture the first startup time individually
+                long firstStartUpTime = launchVideo();
+                writeToOutputFile(firstStartUpTime, "na", true, "Video");
+            } else {
+                startupTime = launchVideo();
+                totalStartupTime += startupTime;
+                individualStartupTime += startupTime + " ,";
+            }
+        }
+        Log.v(TAG, "totalStartupTime =" + totalStartupTime);
+        writeToOutputFile(totalStartupTime, individualStartupTime, false, "Video");
+    }
+
+    @LargeTest
+    public void testLaunchCamera() throws Exception {
+        String individualStartupTime;
+        individualStartupTime = "Individual Camera Startup Time = ";
+        long totalStartupTime = 0;
+        long startupTime = 0;
+        for (int i = 0; i < TOTAL_NUMBER_OF_STARTUP; i++) {
+            if (i == 0) {
+                // Capture the first startup time individually
+                long firstStartUpTime = launchCamera();
+                writeToOutputFile(firstStartUpTime, "na", true, "Camera");
+            } else {
+                startupTime = launchCamera();
+                totalStartupTime += startupTime;
+                individualStartupTime += startupTime + " ,";
+            }
+        }
+        Log.v(TAG, "totalStartupTime =" + totalStartupTime);
+        writeToOutputFile(totalStartupTime,
+                individualStartupTime, false, "Camera");
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/CameraStressTestRunner.java b/tests_camera/src/com/android/camera/stress/CameraStressTestRunner.java
new file mode 100755
index 0000000..4047da0
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/CameraStressTestRunner.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 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.stress;
+
+import android.os.Bundle;
+import android.test.InstrumentationTestRunner;
+import android.test.InstrumentationTestSuite;
+import junit.framework.TestSuite;
+
+public class CameraStressTestRunner extends InstrumentationTestRunner {
+
+    // Default recorder settings
+    public static int mVideoDuration = 20000; // set default to 20 seconds
+    public static int mVideoIterations = 100; // set default to 100 videos
+    public static int mImageIterations = 100; // set default to 100 images
+
+    @Override
+    public TestSuite getAllTests() {
+        TestSuite suite = new InstrumentationTestSuite(this);
+        suite.addTestSuite(ImageCapture.class);
+        suite.addTestSuite(VideoCapture.class);
+        return suite;
+    }
+
+    @Override
+    public ClassLoader getLoader() {
+        return CameraStressTestRunner.class.getClassLoader();
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        String video_iterations = (String) icicle.get("video_iterations");
+        String image_iterations = (String) icicle.get("image_iterations");
+        String video_duration = (String) icicle.get("video_duration");
+
+        if ( video_iterations != null ) {
+            mVideoIterations = Integer.parseInt(video_iterations);
+        }
+        if ( image_iterations != null) {
+            mImageIterations = Integer.parseInt(image_iterations);
+        }
+        if ( video_duration != null) {
+            mVideoDuration = Integer.parseInt(video_duration);
+        }
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/ImageCapture.java b/tests_camera/src/com/android/camera/stress/ImageCapture.java
new file mode 100755
index 0000000..ad06db1
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/ImageCapture.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.stress;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.stress.CameraStressTestRunner;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.app.Activity;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.ImageCapture \
+ *    -w com.google.android.camera.tests/android.test.InstrumentationTestRunner
+ *
+ */
+
+public class ImageCapture extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private String TAG = "ImageCapture";
+    private static final long WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN = 1500;   //1.5 sedconds
+    private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //3 seconds
+
+    private TestUtil testUtil = new TestUtil();
+
+    // Private intent extras.
+    private final static String EXTRAS_CAMERA_FACING =
+        "android.intent.extras.CAMERA_FACING";
+
+    public ImageCapture() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        testUtil.prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        testUtil.closeOutputFile();
+        super.tearDown();
+    }
+
+    public void captureImages(String reportTag, Instrumentation inst) {
+        int total_num_of_images = CameraStressTestRunner.mImageIterations;
+        Log.v(TAG, "no of images = " + total_num_of_images);
+
+        //TODO(yslau): Need to integrate the outoput with the central dashboard,
+        //write to a txt file as a temp solution
+        boolean memoryResult = false;
+        KeyEvent focusEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_FOCUS);
+
+        try {
+            testUtil.writeReportHeader(reportTag, total_num_of_images);
+            for (int i = 0; i < total_num_of_images; i++) {
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                inst.sendKeySync(focusEvent);
+                inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+                Thread.sleep(WAIT_FOR_IMAGE_CAPTURE_TO_BE_TAKEN);
+                testUtil.writeResult(i);
+            }
+        } catch (Exception e) {
+            Log.v(TAG, "Got exception: " + e.toString());
+            assertTrue("testImageCapture", false);
+        }
+    }
+
+    @LargeTest
+    public void testBackImageCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent();
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureImages("Back Camera Image Capture\n", inst);
+        act.finish();
+    }
+
+    @LargeTest
+    public void testFrontImageCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent();
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureImages("Front Camera Image Capture\n", inst);
+        act.finish();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/ShotToShotLatency.java b/tests_camera/src/com/android/camera/stress/ShotToShotLatency.java
new file mode 100644
index 0000000..0c1ef45
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/ShotToShotLatency.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2012 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.stress;
+
+import android.app.Instrumentation;
+import android.os.Environment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.KeyEvent;
+import com.android.camera.CameraActivity;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Junit / Instrumentation test case for measuring camera shot to shot latency
+ */
+public class ShotToShotLatency extends ActivityInstrumentationTestCase2<CameraActivity> {
+    private String TAG = "ShotToShotLatency";
+    private static final int TOTAL_NUMBER_OF_SNAPSHOTS = 250;
+    private static final long SNAPSHOT_WAIT = 1000;
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private static final String CAMERA_IMAGE_DIRECTORY =
+            Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/";
+
+    public ShotToShotLatency() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void cleanupLatencyImages() {
+        try {
+            File sdcard = new File(CAMERA_IMAGE_DIRECTORY);
+            File[] pics = null;
+            FilenameFilter filter = new FilenameFilter() {
+                public boolean accept(File dir, String name) {
+                    return name.endsWith(".jpg");
+                }
+            };
+            pics = sdcard.listFiles(filter);
+            for (File f : pics) {
+                f.delete();
+            }
+        } catch (SecurityException e) {
+            Log.e(TAG, "Security manager access violation: " + e.toString());
+        }
+    }
+
+    private void sleep(long time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Sleep InterruptedException " + e.toString());
+        }
+    }
+
+    @LargeTest
+    public void testShotToShotLatency() {
+        long sigmaOfDiffFromMeanSquared = 0;
+        double mean = 0;
+        double standardDeviation = 0;
+        ArrayList<Long> captureTimes = new ArrayList<Long>();
+        ArrayList<Long> latencyTimes = new ArrayList<Long>();
+
+        Log.v(TAG, "start testShotToShotLatency test");
+        Instrumentation inst = getInstrumentation();
+
+        // Generate data points
+        for (int i = 0; i < TOTAL_NUMBER_OF_SNAPSHOTS; i++) {
+            inst.sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_CENTER);
+            sleep(SNAPSHOT_WAIT);
+            CameraActivity c = getActivity();
+            if (c.getCaptureStartTime() > 0) {
+                captureTimes.add(c.getCaptureStartTime());
+            }
+        }
+
+        // Calculate latencies
+        for (int j = 1; j < captureTimes.size(); j++) {
+            latencyTimes.add(captureTimes.get(j) - captureTimes.get(j - 1));
+        }
+
+        // Crunch numbers
+        for (long dataPoint : latencyTimes) {
+            mean += (double) dataPoint;
+        }
+        mean /= latencyTimes.size();
+
+        for (long dataPoint : latencyTimes) {
+            sigmaOfDiffFromMeanSquared += (dataPoint - mean) * (dataPoint - mean);
+        }
+        standardDeviation = Math.sqrt(sigmaOfDiffFromMeanSquared / latencyTimes.size());
+
+        // Report statistics
+        File outFile = new File(CAMERA_TEST_OUTPUT_FILE);
+        BufferedWriter output = null;
+        try {
+            output = new BufferedWriter(new FileWriter(outFile, true));
+            output.write("Shot to shot latency - mean: " + mean + "\n");
+            output.write("Shot to shot latency - standard deviation: " + standardDeviation + "\n");
+            cleanupLatencyImages();
+        } catch (IOException e) {
+            Log.e(TAG, "testShotToShotLatency IOException writing to log " + e.toString());
+        } finally {
+            try {
+                if (output != null) {
+                    output.close();
+                }
+            } catch (IOException e) {
+                Log.e(TAG, "Error closing file: " + e.toString());
+            }
+        }
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/SwitchPreview.java b/tests_camera/src/com/android/camera/stress/SwitchPreview.java
new file mode 100755
index 0000000..86b1b5d
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/SwitchPreview.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.stress;
+
+import com.android.camera.CameraActivity;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.SwitchPreview \
+ *    -w com.android.camera.tests/com.android.camera.stress.CameraStressTestRunner
+ *
+ */
+public class SwitchPreview extends ActivityInstrumentationTestCase2 <CameraActivity>{
+    private String TAG = "SwitchPreview";
+    private static final int TOTAL_NUMBER_OF_SWITCHING = 200;
+    private static final long WAIT_FOR_PREVIEW = 4000;
+
+    private static final String CAMERA_TEST_OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+    private BufferedWriter mOut;
+    private FileWriter mfstream;
+
+    public SwitchPreview() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        getActivity();
+        prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getActivity().finish();
+        closeOutputFile();
+        super.tearDown();
+    }
+
+    private void prepareOutputFile(){
+        try{
+            mfstream = new FileWriter(CAMERA_TEST_OUTPUT_FILE, true);
+            mOut = new BufferedWriter(mfstream);
+        } catch (Exception e){
+            assertTrue("Camera Switch Mode", false);
+        }
+    }
+
+    private void closeOutputFile() {
+        try {
+            mOut.write("\n");
+            mOut.close();
+            mfstream.close();
+        } catch (Exception e) {
+            assertTrue("CameraSwitchMode close output", false);
+        }
+    }
+
+    @LargeTest
+    public void testSwitchMode() {
+        //Switching the video and the video recorder mode
+        Instrumentation inst = getInstrumentation();
+        try{
+            mOut.write("Camera Switch Mode:\n");
+            mOut.write("No of loops :" + TOTAL_NUMBER_OF_SWITCHING + "\n");
+            mOut.write("loop: ");
+            for (int i=0; i< TOTAL_NUMBER_OF_SWITCHING; i++) {
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                intent.setClass(getInstrumentation().getTargetContext(),
+                        CameraActivity.class);
+                getActivity().startActivity(intent);
+                Thread.sleep(WAIT_FOR_PREVIEW);
+                intent = new Intent();
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                intent.setClass(getInstrumentation().getTargetContext(),
+                        CameraActivity.class);
+                getActivity().startActivity(intent);
+                mOut.write(" ," + i);
+                mOut.flush();
+            }
+        } catch (Exception e){
+            Log.v(TAG, "Got exception", e);
+        }
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/TestUtil.java b/tests_camera/src/com/android/camera/stress/TestUtil.java
new file mode 100644
index 0000000..64e2039
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/TestUtil.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 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.stress;
+
+import android.os.Environment;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+
+
+/**
+ * Collection of utility functions used for the test.
+ */
+public class TestUtil {
+    public BufferedWriter mOut;
+    public FileWriter mfstream;
+
+    public TestUtil() {
+    }
+
+    public void prepareOutputFile() throws Exception {
+        String camera_test_output_file =
+                Environment.getExternalStorageDirectory().toString() + "/mediaStressOut.txt";
+        mfstream = new FileWriter(camera_test_output_file, true);
+        mOut = new BufferedWriter(mfstream);
+    }
+
+    public void closeOutputFile() throws Exception {
+        mOut.write("\n");
+        mOut.close();
+        mfstream.close();
+    }
+
+    public void writeReportHeader(String reportTag, int iteration) throws Exception {
+        mOut.write(reportTag);
+        mOut.write("No of loops :" + iteration + "\n");
+        mOut.write("loop: ");
+    }
+
+    public void writeResult(int iteration) throws Exception {
+        mOut.write(" ," + iteration);
+        mOut.flush();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/stress/VideoCapture.java b/tests_camera/src/com/android/camera/stress/VideoCapture.java
new file mode 100755
index 0000000..ec55ccc
--- /dev/null
+++ b/tests_camera/src/com/android/camera/stress/VideoCapture.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2010 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.stress;
+
+import com.android.camera.CameraActivity;
+import com.android.camera.stress.TestUtil;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.provider.MediaStore;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.KeyEvent;
+
+import com.android.camera.stress.CameraStressTestRunner;
+
+/**
+ * Junit / Instrumentation test case for camera test
+ *
+ * Running the test suite:
+ *
+ * adb shell am instrument \
+ *    -e class com.android.camera.stress.VideoCapture \
+ *    -w com.google.android.camera.tests/android.test.InstrumentationTestRunner
+ *
+ */
+
+public class VideoCapture extends ActivityInstrumentationTestCase2 <CameraActivity> {
+    private static final long WAIT_FOR_PREVIEW = 1500; //1.5 seconds
+    private static final long WAIT_FOR_SWITCH_CAMERA = 3000; //2 seconds
+
+    // Private intent extras which control the camera facing.
+    private final static String EXTRAS_CAMERA_FACING =
+        "android.intent.extras.CAMERA_FACING";
+
+    private TestUtil testUtil = new TestUtil();
+
+    public VideoCapture() {
+        super(CameraActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        testUtil.prepareOutputFile();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        testUtil.closeOutputFile();
+        super.tearDown();
+    }
+
+    @LargeTest
+    public void captureVideos(String reportTag, Instrumentation inst) throws Exception{
+        boolean memoryResult = false;
+        int total_num_of_videos = CameraStressTestRunner.mVideoIterations;
+        int video_duration = CameraStressTestRunner.mVideoDuration;
+        testUtil.writeReportHeader(reportTag, total_num_of_videos);
+
+        for (int i = 0; i < total_num_of_videos; i++) {
+            Thread.sleep(WAIT_FOR_PREVIEW);
+            // record a video
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+            Thread.sleep(video_duration);
+            inst.sendCharacterSync(KeyEvent.KEYCODE_CAMERA);
+            testUtil.writeResult(i);
+        }
+    }
+
+    @LargeTest
+    public void testBackVideoCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureVideos("Back Camera Video Capture\n", inst);
+        act.finish();
+    }
+
+    @LargeTest
+    public void testFrontVideoCapture() throws Exception {
+        Instrumentation inst = getInstrumentation();
+        Intent intent = new Intent(MediaStore.INTENT_ACTION_VIDEO_CAMERA);
+
+        intent.setClass(getInstrumentation().getTargetContext(), CameraActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRAS_CAMERA_FACING,
+                android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT);
+        Activity act = inst.startActivitySync(intent);
+        Thread.sleep(WAIT_FOR_SWITCH_CAMERA);
+        captureVideos("Front Camera Video Capture\n", inst);
+        act.finish();
+    }
+}
diff --git a/tests_camera/src/com/android/camera/unittest/CameraUnitTest.java b/tests_camera/src/com/android/camera/unittest/CameraUnitTest.java
new file mode 100644
index 0000000..0b4fc80
--- /dev/null
+++ b/tests_camera/src/com/android/camera/unittest/CameraUnitTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2010 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.unittest;
+
+import com.android.camera.Util;
+
+import android.graphics.Matrix;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+@SmallTest
+public class CameraUnitTest extends TestCase {
+    public void testRoundOrientation() {
+        int h = Util.ORIENTATION_HYSTERESIS;
+        assertEquals(0, Util.roundOrientation(0, 0));
+        assertEquals(0, Util.roundOrientation(359, 0));
+        assertEquals(0, Util.roundOrientation(0 + 44 + h, 0));
+        assertEquals(90, Util.roundOrientation(0 + 45 + h, 0));
+        assertEquals(0, Util.roundOrientation(360 - 44 - h, 0));
+        assertEquals(270, Util.roundOrientation(360 - 45 - h, 0));
+
+        assertEquals(90, Util.roundOrientation(90, 90));
+        assertEquals(90, Util.roundOrientation(90 + 44 + h, 90));
+        assertEquals(180, Util.roundOrientation(90 + 45 + h, 90));
+        assertEquals(90, Util.roundOrientation(90 - 44 - h, 90));
+        assertEquals(0, Util.roundOrientation(90 - 45 - h, 90));
+
+        assertEquals(180, Util.roundOrientation(180, 180));
+        assertEquals(180, Util.roundOrientation(180 + 44 + h, 180));
+        assertEquals(270, Util.roundOrientation(180 + 45 + h, 180));
+        assertEquals(180, Util.roundOrientation(180 - 44 - h, 180));
+        assertEquals(90, Util.roundOrientation(180 - 45 - h, 180));
+
+        assertEquals(270, Util.roundOrientation(270, 270));
+        assertEquals(270, Util.roundOrientation(270 + 44 + h, 270));
+        assertEquals(0, Util.roundOrientation(270 + 45 + h, 270));
+        assertEquals(270, Util.roundOrientation(270 - 44 - h, 270));
+        assertEquals(180, Util.roundOrientation(270 - 45 - h, 270));
+
+        assertEquals(90, Util.roundOrientation(90, 0));
+        assertEquals(180, Util.roundOrientation(180, 0));
+        assertEquals(270, Util.roundOrientation(270, 0));
+
+        assertEquals(0, Util.roundOrientation(0, 90));
+        assertEquals(180, Util.roundOrientation(180, 90));
+        assertEquals(270, Util.roundOrientation(270, 90));
+
+        assertEquals(0, Util.roundOrientation(0, 180));
+        assertEquals(90, Util.roundOrientation(90, 180));
+        assertEquals(270, Util.roundOrientation(270, 180));
+
+        assertEquals(0, Util.roundOrientation(0, 270));
+        assertEquals(90, Util.roundOrientation(90, 270));
+        assertEquals(180, Util.roundOrientation(180, 270));
+    }
+
+    public void testPrepareMatrix() {
+        Matrix matrix = new Matrix();
+        float[] points;
+        int[] expected;
+
+        Util.prepareMatrix(matrix, false, 0, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {0, 0, 400, 240, 800, 480, 400, 480, 100, 300};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, false, 90, 800, 480);
+        points = new float[] {-1000, -1000,   0,   0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {800, 0, 400, 240, 0, 480, 0, 240, 300, 60};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, false, 180, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {800, 480, 400, 240, 0, 0, 400, 0, 700, 180};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+
+        Util.prepareMatrix(matrix, true, 180, 800, 480);
+        points = new float[] {-1000, -1000, 0, 0, 1000, 1000, 0, 1000, -750, 250};
+        expected = new int[] {0, 480, 400, 240, 800, 0, 400, 0, 100, 180};
+        matrix.mapPoints(points);
+        assertEquals(expected, points);
+    }
+
+    private void assertEquals(int expected[], float[] actual) {
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals("Array index " + i + " mismatch", expected[i], Math.round(actual[i]));
+        }
+    }
+}